Add process UId and author GId support in no-smack mode
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Thu, 2 Jan 2025 14:37:39 +0000 (15:37 +0100)
committerTomasz Swierczek <t.swierczek@samsung.com>
Wed, 5 Feb 2025 07:56:19 +0000 (08:56 +0100)
The process UId is an equivalent of a process smack label. Therefore,
it must follow the hybridity rules:
1. For non-hybrid apps, all apps in the package must have the same
process UId.
2. For hybrid apps, each app must have a different process UId.
To achieve that, the process UId equal to pkg_id for non-hybrid apps
and equal to app_id for hybrid apps.

The author GId is always equal to author_id.

During app installation in no-smack mode, the pkg_id, app_id and
author_id are selected as follows:
1. author_id is set to the smallest unused value starting from 20000.
2. pkg_id and app_id are set to the smallest value unused in both
pkg_id from table pkg and app_id from table app starting from 10000.

To achieve that, the StmtType::EAddApplication and
user_app_pkg_view_insert_trigger have been adjusted to accept 3
additional arguments (pkg_id, app_id and author_id). Setting them to
null in smack-enabled mode makes sqlite assign these values as before
(see https://www.sqlite.org/quirks.html#primary_keys_can_sometimes_contain_nulls).

If an app being installed in no-smack mode belongs to an existing
package or author, the corresponding "INSERT OR IGNORE" on pkg or
author table will fail, making the rest of the query reuse existing
values.

Add statements facilitating pkg_id, app_id & author_id lookup.

Bump db version to apply view change.

Add unit tests (test suite: PRIVILEGE_DB_TEST_PROCESS_UID_AUTHOR_GID).

Change-Id: I7cfaf7bc552b7ee3b1166024707f22d8af7c1a8d

db/db.sql
db/updates/update-db-to-v16.sql [new file with mode: 0644]
src/common/include/privilege_db.h
src/common/privilege_db.cpp
test/CMakeLists.txt
test/test_privilege_db_add_app.cpp

index 539b5394ea61cf153f204f24d87342d91f6775df..dee32f034be43d266473aecda711e0e5ccd6461a 100644 (file)
--- a/db/db.sql
+++ b/db/db.sql
@@ -4,7 +4,7 @@ PRAGMA auto_vacuum = NONE;
 
 BEGIN EXCLUSIVE TRANSACTION;
 
-PRAGMA user_version = 15;
+PRAGMA user_version = 16;
 
 CREATE TABLE IF NOT EXISTS pkg (
 pkg_id INTEGER PRIMARY KEY,
@@ -118,9 +118,10 @@ BEGIN
                       AND NEW.author_hash IS NOT NULL
                       AND author_hash!=NEW.author_hash);
 
-    INSERT OR IGNORE INTO author(hash) VALUES (NEW.author_hash);
+    INSERT OR IGNORE INTO author(author_id, hash) VALUES (NEW.author_id, NEW.author_hash);
 
-    INSERT OR IGNORE INTO pkg(name, author_id, is_hybrid) VALUES (
+    INSERT OR IGNORE INTO pkg(pkg_id, name, author_id, is_hybrid) VALUES (
+        NEW.pkg_id,
         NEW.pkg_name,
         (SELECT author_id FROM author WHERE hash=NEW.author_hash),
         NEW.is_hybrid);
@@ -135,8 +136,9 @@ BEGIN
     -- If app have already existed with different version do update it
     UPDATE app SET version=NEW.version WHERE name=NEW.app_name;
 
-    INSERT OR IGNORE INTO app (pkg_id, name, version) VALUES (
+    INSERT OR IGNORE INTO app (pkg_id, app_id, name, version) VALUES (
         (SELECT pkg_id FROM pkg WHERE name=NEW.pkg_name),
+        NEW.app_id,
         NEW.app_name,
         NEW.version);
 
diff --git a/db/updates/update-db-to-v16.sql b/db/updates/update-db-to-v16.sql
new file mode 100644 (file)
index 0000000..0fc4168
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN EXCLUSIVE TRANSACTION;
+
+PRAGMA user_version = 16;
+
+COMMIT TRANSACTION;
index 39b9d3d4517fb20566e1b388014efe45c33a6f81..7c2052357450731ccba9d0a5e88cdfdc87ac6b63 100644 (file)
@@ -88,8 +88,12 @@ enum class StmtType : uint8_t {
     EGetLicenseForClientPrivilegeAndApp,
     EGetLicenseForClientPrivilegeAndPkg,
     EIsUserPkgInstalled,
+    EGetSortedProcessUIds,
+    EGetSortedAuthorGIds,
+    EGetProcessUIdInfo,
+    EGetAuthorId,
 };
-enum : uint8_t { StmtTypeCount = underlying(StmtType::EIsUserPkgInstalled) + 1 };
+enum : uint8_t { StmtTypeCount = underlying(StmtType::EGetAuthorId) + 1 };
 
 // privilege, app_defined_privilege_type, license
 typedef std::tuple<std::string, int, std::string> AppDefinedPrivilege;
@@ -147,7 +151,28 @@ private:
         static auto loaderCmd() { return "/usr/bin/security-manager-rules-loader"; }
     };
 
+    /**
+     * Return first free id using provided statement starting from given value
+     *
+     * @param startValue starting value for id
+     * @param statement query used to extract existing ids sorted in ascending order
+     */
+    int GetFirstFreeId(int startValue, StmtType statement);
+
+    /**
+     * Return first id not used as pkg_id nor app_id
+     */
+    int GetFirstFreeProcessUId();
+
+    /**
+     * Return first free author_id from author table
+     */
+    int GetFirstFreeAuthorGId();
+
 public:
+    static constexpr int PROCESS_UID_MIN = 10000;
+    static constexpr int AUTHOR_GID_MIN = 20000;
+
     class Exception
     {
       public:
@@ -632,6 +657,29 @@ public:
      * @exception PrivilegeDb::Exception::ConstraintError on constraint violation
      */
     bool IsUserPkgInstalled(const std::string& pkgName, uid_t uid);
+
+    /**
+     * Return process UID for given app & package
+     *
+     * @param[in]  pkgName - application package
+     * @param[in]  appName - application identifier
+     * @param[out] processUId - process UId
+     *
+     * @exception PrivilegeDb::Exception::InternalError on internal error
+     * @exception PrivilegeDb::Exception::ConstraintError on constraint violation
+     */
+    bool GetProcessUId(const std::string& pkgName, const std::string& appName, uid_t& processUId);
+
+    /**
+     * Return author GId for given package
+     *
+     * @param[in]  pkgName - application package
+     * @param[out] authorGId - author GId
+     *
+     * @exception PrivilegeDb::Exception::InternalError on internal error
+     * @exception PrivilegeDb::Exception::ConstraintError on constraint violation
+     */
+    bool GetAuthorGId(const std::string& pkgName, gid_t& authorGId);
 };
 
 } //namespace SecurityManager
index 31d4544a6b82f257a3cb0cb0d30821a4b9ea9e74..72192665481a2d97524f4f70cb9dddd64f5b5522 100644 (file)
 #include "privilege_db.h"
 #include "tzplatform-config.h"
 #include "filesystem.h"
+#include "smack-check.h"
 
 namespace SecurityManager {
 namespace {
 
 constexpr const char *g_queries[StmtTypeCount] = {
-    [underlying(StmtType::EAddApplication)] = "INSERT INTO user_app_pkg_view (app_name, pkg_name, uid, version, author_hash, is_hybrid)"
-                                              " VALUES (?, ?, ?, ?, ?, ?)",
+    [underlying(StmtType::EAddApplication)] = "INSERT INTO user_app_pkg_view (app_name, pkg_name, uid, version, author_hash, is_hybrid, pkg_id, app_id, author_id)"
+                                              " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
     [underlying(StmtType::ERemoveApplication)] = "DELETE FROM user_app_pkg_view WHERE app_name=? AND uid=?",
     [underlying(StmtType::EPkgNameExists)] = "SELECT count(*) FROM pkg WHERE name=?",
     [underlying(StmtType::EAppNameExists)] = "SELECT count(*) FROM app WHERE name=?",
@@ -86,6 +87,10 @@ constexpr const char *g_queries[StmtTypeCount] = {
     [underlying(StmtType::EGetLicenseForClientPrivilegeAndApp)] = "SELECT license FROM client_license_view WHERE app_name = ? AND uid = ? AND privilege = ? ",
     [underlying(StmtType::EGetLicenseForClientPrivilegeAndPkg)] = "SELECT license FROM client_license_view WHERE pkg_name = ? AND uid = ? AND privilege = ? ",
     [underlying(StmtType::EIsUserPkgInstalled)] = "SELECT count(*) FROM user_app_pkg_view WHERE pkg_name = ? AND uid = ?",
+    [underlying(StmtType::EGetSortedProcessUIds)] = "SELECT pkg_id FROM pkg UNION SELECT app_id FROM app ORDER BY pkg_id",
+    [underlying(StmtType::EGetSortedAuthorGIds)] = "SELECT author_id FROM author ORDER BY author_id",
+    [underlying(StmtType::EGetProcessUIdInfo)] = "SELECT pkg_id, app_id, is_hybrid FROM user_app_pkg_view WHERE pkg_name = ? AND app_name = ?",
+    [underlying(StmtType::EGetAuthorId)] = "SELECT author_id FROM pkg WHERE name = ?",
 };
 static_assert(allTrue(g_queries));
 
@@ -318,6 +323,16 @@ void PrivilegeDb::AddApplication(
         command->BindString(4, targetTizenVer);
         authorName.empty() ? command->BindNull(5) : command->BindString(5, getAuthorHash(authorName));
         command->BindInteger(6, isHybrid ? 1 : 0);
+        if (smack_simple_check()) {
+            command->BindNull(7);
+            command->BindNull(8);
+            command->BindNull(9);
+        } else {
+            auto processUId = GetFirstFreeProcessUId();
+            command->BindInteger(7, processUId);
+            command->BindInteger(8, processUId);
+            command->BindInteger(9, GetFirstFreeAuthorGId());
+        }
 
         if (command->Step()) {
             LogDebug("Unexpected SQLITE_ROW answer to query: " <<
@@ -835,4 +850,64 @@ bool PrivilegeDb::IsUserPkgInstalled(const std::string& pkgName, uid_t uid)
     });
 }
 
+int PrivilegeDb::GetFirstFreeId(int startValue, StmtType statement)
+{
+    auto command = getStatement(statement);
+    int id = startValue;
+
+    while (command->Step()) {
+        if (id != command->GetColumnInteger(0))
+            break;
+        id++;
+    };
+    return id;
+}
+
+int PrivilegeDb::GetFirstFreeProcessUId()
+{
+    return GetFirstFreeId(PROCESS_UID_MIN, StmtType::EGetSortedProcessUIds);
+}
+
+int PrivilegeDb::GetFirstFreeAuthorGId()
+{
+    return GetFirstFreeId(AUTHOR_GID_MIN, StmtType::EGetSortedAuthorGIds);
+}
+
+bool PrivilegeDb::GetProcessUId(
+        const std::string& pkgName,
+        const std::string& appName,
+        uid_t& processUId)
+{
+    return try_catch_db(m_api_mutex, [&]() -> bool {
+        auto command = getStatement(StmtType::EGetProcessUIdInfo);
+        command->BindString(1, pkgName);
+        command->BindString(2, appName);
+
+        if (command->Step()) {
+            int pkgId = command->GetColumnInteger(0);
+            int appId = command->GetColumnInteger(1);
+            int hybrid = command->GetColumnInteger(2);
+            processUId = hybrid ? appId : pkgId;
+            return true;
+        }
+
+        return false;
+    });
+}
+
+bool PrivilegeDb::GetAuthorGId(const std::string& pkgName, gid_t& authorGId)
+{
+    return try_catch_db(m_api_mutex, [&]() -> bool {
+        auto command = getStatement(StmtType::EGetAuthorId);
+        command->BindString(1, pkgName);
+
+        if (command->Step()) {
+            authorGId = command->GetColumnInteger(0);
+            return true;
+        }
+
+        return false;
+    });
+}
+
 } //namespace SecurityManager
index 14444a6bbc0fc8d4c5001c784e7dc5392f27e100..413f42d023b466d5125c52a13e0243b81331aaf5 100644 (file)
@@ -147,7 +147,7 @@ SET(SM_PERFORMANCE_TESTS_SOURCES
     ${PROJECT_SOURCE_DIR}/src/common/config-file.cpp
     #${PROJECT_SOURCE_DIR}/src/common/file-lock.cpp
     ${PROJECT_SOURCE_DIR}/src/common/privilege_db.cpp
-    #${PROJECT_SOURCE_DIR}/src/common/smack-check.cpp
+    ${PROJECT_SOURCE_DIR}/src/common/smack-check.cpp
     #${PROJECT_SOURCE_DIR}/src/common/smack-labels.cpp
     #${PROJECT_SOURCE_DIR}/src/common/smack-rules.cpp
     ${PROJECT_SOURCE_DIR}/src/common/filesystem.cpp
index 63a6bdce54edaaa1c3f89c095ad6c3dc41e938e4..2968b30bd1720e83992131bdbc93aff77d31bab8 100644 (file)
@@ -223,3 +223,169 @@ POSITIVE_TEST_CASE(T650_update_applications_with_upper_tizen_version)
 }
 
 BOOST_AUTO_TEST_SUITE_END()
+
+#ifndef SMACK_ENABLED
+
+class PrivilegeDBFixtureExt : public PrivilegeDBFixture {
+public:
+    void checkProcessUId(int appIdx, int pkgIdx, uid_t expected) {
+        uid_t got;
+        BOOST_REQUIRE_MESSAGE(getPrivDb()->GetProcessUId(pkg(pkgIdx), app(appIdx), got), "GetProcessUId failed");
+        BOOST_REQUIRE_MESSAGE(got == expected, "Wrong process UId");
+    }
+
+    void checkAuthorGId(int pkgIdx, gid_t expected) {
+        gid_t got;
+        BOOST_REQUIRE_MESSAGE(getPrivDb()->GetAuthorGId(pkg(pkgIdx), got), "GetAuthorGId failed");
+        BOOST_REQUIRE_MESSAGE(got == expected, "Wrong author GId");
+    }
+
+    static gid_t ProcessUId(size_t idx) {
+        return PrivilegeDb::PROCESS_UID_MIN + idx;
+    }
+
+    static gid_t AuthorGId(size_t idx) {
+        return PrivilegeDb::AUTHOR_GID_MIN + idx;
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(PRIVILEGE_DB_TEST_PPROCESS_UID_AUTHOR_GID, PrivilegeDBFixtureExt)
+
+POSITIVE_TEST_CASE(T1000_process_uid_non_hybrid)
+{
+    // NOTE: This test requires empty database to work
+
+    addAppSuccess(app(1), pkg(1), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(2), pkg(1), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(3), pkg(2), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(4), pkg(3), uid(1), tizenVer(1), author(1), NotHybrid);
+
+    // appNo  appId  pkgId
+    // 1      10000  10000
+    // 2      10001  10000
+    // 3      10002  10002
+    // 4      10003  10003
+
+    checkProcessUId(1, 1, ProcessUId(0));
+    checkProcessUId(2, 1, ProcessUId(0));
+    checkProcessUId(3, 2, ProcessUId(2));
+    checkProcessUId(4, 3, ProcessUId(3));
+
+    removeAppSuccess(app(1), uid(1));
+    removeAppSuccess(app(3), uid(1));
+
+    // appNo  appId  pkgId
+    // 2      10001  10000
+    // 4      10003  10003
+
+    uid_t got;
+    BOOST_REQUIRE_MESSAGE(!getPrivDb()->GetProcessUId(pkg(1), app(1), got), "GetProcessUId should fail");
+    BOOST_REQUIRE_MESSAGE(!getPrivDb()->GetProcessUId(pkg(3), app(1), got), "GetProcessUId should fail");
+
+    addAppSuccess(app(5), pkg(4), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(6), pkg(4), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(7), pkg(5), uid(1), tizenVer(1), author(1), NotHybrid);
+
+    // appNo  appId  pkgId
+    // 2      10001  10000
+    // 4      10003  10003
+    // 5      10002  10002
+    // 6      10004  10002
+    // 7      10005  10005
+
+    checkProcessUId(2, 1, ProcessUId(0));
+    checkProcessUId(4, 3, ProcessUId(3));
+    checkProcessUId(5, 4, ProcessUId(2));
+    checkProcessUId(6, 4, ProcessUId(2));
+    checkProcessUId(7, 5, ProcessUId(5));
+}
+
+POSITIVE_TEST_CASE(T1010_process_uid_hybrid)
+{
+    // NOTE: This test requires empty database to work
+
+    addAppSuccess(app(1), pkg(1), uid(1), tizenVer(1), author(1), Hybrid);
+    addAppSuccess(app(2), pkg(1), uid(1), tizenVer(1), author(1), Hybrid);
+    addAppSuccess(app(3), pkg(2), uid(1), tizenVer(1), author(1), Hybrid);
+
+    // appNo  appId  pkgId
+    // 1      10000  10000
+    // 2      10001  10000
+    // 3      10002  10002
+
+    checkProcessUId(1, 1, ProcessUId(0));
+    checkProcessUId(2, 1, ProcessUId(1));
+    checkProcessUId(3, 2, ProcessUId(2));
+
+    removeAppSuccess(app(2), uid(1));
+
+    // appNo  appId  pkgId
+    // 1      10000  10000
+    // 3      10002  10002
+
+    uid_t got;
+    BOOST_REQUIRE_MESSAGE(!getPrivDb()->GetProcessUId(pkg(1), app(2), got), "GetProcessUId should fail");
+
+    addAppSuccess(app(4), pkg(3), uid(1), tizenVer(1), author(1), Hybrid);
+    addAppSuccess(app(5), pkg(3), uid(1), tizenVer(1), author(1), Hybrid);
+    addAppSuccess(app(6), pkg(4), uid(1), tizenVer(1), author(1), Hybrid);
+
+    // appNo  appId  pkgId
+    // 1      10000  10000
+    // 3      10002  10002
+    // 4      10001  10001
+    // 5      10003  10001
+    // 6      10004  10004
+
+    checkProcessUId(1, 1, ProcessUId(0));
+    checkProcessUId(3, 2, ProcessUId(2));
+    checkProcessUId(4, 3, ProcessUId(1));
+    checkProcessUId(5, 3, ProcessUId(3));
+    checkProcessUId(6, 4, ProcessUId(4));
+}
+
+POSITIVE_TEST_CASE(T1020_author_gid)
+{
+    // NOTE: This test requires empty database to work
+
+    addAppSuccess(app(1), pkg(1), uid(1), tizenVer(1), author(1), NotHybrid);
+    addAppSuccess(app(2), pkg(2), uid(1), tizenVer(1), author(2), NotHybrid);
+    addAppSuccess(app(3), pkg(3), uid(1), tizenVer(1), author(2), NotHybrid);
+
+    // pkgNo  authorId
+    // 1      20000
+    // 2      20001
+    // 3      20001
+
+    checkAuthorGId(1, AuthorGId(0));
+    checkAuthorGId(2, AuthorGId(1));
+    checkAuthorGId(3, AuthorGId(1));
+
+    removeAppSuccess(app(1), uid(1));
+    removeAppSuccess(app(2), uid(1));
+
+    // pkgNo  authorId
+    // 3      20001
+
+    gid_t got;
+    BOOST_REQUIRE_MESSAGE(!getPrivDb()->GetAuthorGId(pkg(1), got), "GetAuthorGId should fail");
+    BOOST_REQUIRE_MESSAGE(!getPrivDb()->GetAuthorGId(pkg(2), got), "GetAuthorGId should fail");
+
+    addAppSuccess(app(4), pkg(4), uid(1), tizenVer(1), author(3), NotHybrid);
+    addAppSuccess(app(5), pkg(5), uid(1), tizenVer(1), author(3), NotHybrid);
+    addAppSuccess(app(6), pkg(6), uid(1), tizenVer(1), author(4), NotHybrid);
+
+    // pkgNo  authorId
+    // 3      20001
+    // 4      20000
+    // 5      20000
+    // 6      20002
+
+    checkAuthorGId(3, AuthorGId(1));
+    checkAuthorGId(4, AuthorGId(0));
+    checkAuthorGId(5, AuthorGId(0));
+    checkAuthorGId(6, AuthorGId(2));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+#endif