+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "app_install_helper_ext.h"
+
+#include <grp.h>
+#include <unistd.h>
+#include <sys/smack.h>
+
+#include <utility>
+#include <string>
+#include <unordered_set>
+#include <algorithm>
+
+#include <security-manager-types.h>
+
+#include <sm_api.h>
+#include <label_generator.h>
+#include <cynara_test_client.h>
+
+namespace {
+constexpr char SMACK_RULES_PATH[] = "/sys/fs/smackfs/load2";
+constexpr char ANY_USER_REPRESENTATION[] = "anyuser";/*this may be actually any string*/
+
+void assertNoLabelInRule(const AccessRequest &rule, const std::string &label)
+{
+ RUNNER_ASSERT_MSG(rule.object != label && rule.subject != label,
+ "Smack rule left after uninstallation process." <<
+ " Subject: " << rule.subject <<
+ " object: " << rule.object <<
+ " access: " << rule.access);
+}
+
+std::string accessOpposite(std::string &access)
+{
+ static const std::map<char, int> accessMapping = {{'r', 0}, {'w', 1}, {'x', 2}, {'a', 3},
+ {'t', 4}, {'l', 5}};
+ // May write implies may lock
+ if (access.find('w') != std::string::npos && access.find('l') == std::string::npos) {
+ access.append("l");
+ }
+ std::string oppositeAccess = "rwxatl";
+ for (char c : access) {
+ oppositeAccess[accessMapping.at(c)] = '-';
+ }
+ auto it = std::remove_if(oppositeAccess.begin(), oppositeAccess.end(), [](char c) {return c == '-';});
+ oppositeAccess.erase(it, oppositeAccess.end());
+ return oppositeAccess;
+}
+
+void checkExactSmackAccesses(const std::string &subject, const std::string &object,
+ const std::string &access)
+{
+ std::string access_str(access);
+ auto no_access = accessOpposite(access_str);
+ for (char c : access_str) {
+ int ret = smack_have_access(subject.c_str(), object.c_str(), std::string(1, c).c_str());
+ RUNNER_ASSERT_MSG(ret >= 0, "smack_have_access failed: <" << subject << ">, <" << object
+ << ">, <" << c << "> errno=" << strerror(errno));
+ RUNNER_ASSERT_MSG(ret == 1, "Access " << c << " from " << subject << " to "
+ << object << " not given");
+ }
+
+ for (char c : no_access) {
+ int ret = smack_have_access(subject.c_str(), object.c_str(), std::string(1, c).c_str());
+ RUNNER_ASSERT_MSG(ret >= 0, "smack_have_access failed: <" << subject << ">, <" << object
+ << ">, <" << c << "> errno=" << strerror(errno));
+ RUNNER_ASSERT_MSG(ret == 0, "Access " << c << " from " << subject << " to "
+ << object << " unnecessarily given");
+ }
+}
+
+} // namespace anonymous
+
+namespace SecurityManagerTest
+{
+
+void AppInstallHelperExt::checkPrivileges(const PolicyConfiguration::PrivVector &allowedPrivs,
+ const PolicyConfiguration::PrivVector &deniedPrivs) const
+{
+ /* Privileges should be granted to all users if root installs app */
+ auto user = (m_uidGid == 0 ? ANY_USER_REPRESENTATION : std::to_string(m_uidGid));
+
+ std::string smackLabel = generateProcessLabel(m_appName, m_pkgName, m_isHybrid);
+
+ CynaraTestClient::Client ctc;
+ int result;
+
+ for (auto &priv : allowedPrivs) {
+ ctc.check(smackLabel.c_str(), "", user, priv.c_str(), CYNARA_API_ACCESS_ALLOWED);
+
+ Api::appHasPrivilege(m_appName, priv, m_uidGid, result);
+ RUNNER_ASSERT_MSG(result == 1,
+ "Application " << m_appName << " has no access to " << priv);
+ }
+
+ for (auto &priv : deniedPrivs) {
+ ctc.check(smackLabel.c_str(), "", user, priv.c_str(), CYNARA_API_ACCESS_DENIED);
+
+ Api::appHasPrivilege(m_appName, priv, m_uidGid, result);
+ RUNNER_ASSERT_MSG(result == 0,
+ "Application " << m_appName << " has unexpected access to " << priv);
+ }
+}
+
+void AppInstallHelperExt::checkDeniedPrivileges(const PolicyConfiguration::PrivVector &deniedPrivs) const
+{
+ checkPrivileges({}, deniedPrivs);
+}
+
+void AppInstallHelperExt::checkPrivilegeGroups(const PolicyConfiguration::PrivVector &allowedPrivs) const
+{
+ static PolicyConfiguration policy;
+ const auto allowed_groups = policy.privToGroup(allowedPrivs);
+ RUNNER_ASSERT_MSG(allowed_groups.size() == allowedPrivs.size(),
+ "Some privileges given were not found in the policy");
+
+ std::vector<gid_t> allowed_gids;
+ for (const auto &groupName : allowed_groups) {
+ errno = 0;
+ struct group* grp = getgrnam(groupName.c_str());
+ RUNNER_ASSERT_ERRNO_MSG(grp, "Group: " << groupName << " not found");
+ allowed_gids.push_back(grp->gr_gid);
+ }
+
+ checkGids(allowed_gids);
+}
+
+void AppInstallHelperExt::checkAfterInstall() const
+{
+ static const std::vector<AccessRequest> staticRules[] =
+ {parseSmackRulesFile(PolicyConfiguration::getPkgRulesFilePath()),
+ parseSmackRulesFile(PolicyConfiguration::getAppRulesFilePath())};
+
+ checkAppIdExistence(true);
+
+ checkSmackAccesses(staticRules[m_isHybrid]);
+
+ checkPrivileges(getPrivilegesNames(), {});
+}
+
+void AppInstallHelperExt::checkAfterUninstall(bool removePkg) const
+{
+ checkAppIdExistence(false);
+
+ if (removePkg) {
+ checkPkgSmackRulesAfterUninstall();
+ }
+ // there should be no hybrid rules regardless of the app type
+ checkHybridAppSmackRulesAterUninstall();
+
+ checkDeniedPrivileges(getPrivilegesNames());
+}
+
+void AppInstallHelperExt::checkSmackAccesses(std::vector<AccessRequest> rules, bool hasAccess) const
+{
+ const std::pair<std::string, std::string> switchAliases[] = {
+ {"~PATH_RW~", generatePathRWLabel(m_pkgName)},
+ {"~PATH_RO~", generatePathROLabel(m_pkgName)},
+ {"~PROCESS~", generateProcessLabel(m_appName, m_pkgName, m_isHybrid)}
+ };
+
+ for (auto& rule : rules) {
+ if (rule.object == "~PATH_TRUSTED~") {
+ continue;
+ }
+
+ for (const auto &alias : switchAliases) {
+ if (rule.subject == alias.first) {
+ rule.subject = alias.second;
+ }
+ if (rule.object == alias.first) {
+ rule.object = alias.second;
+ }
+ }
+
+ if (!hasAccess)
+ rule.access = "";
+
+ // Special label that may occur in the template. Other special labels will not be used.
+ if (rule.object == "_") {
+ rule.access = "rx" + rule.access;
+ }
+
+ checkExactSmackAccesses(rule.subject, rule.object, rule.access);
+ }
+}
+
+void AppInstallHelperExt::checkPkgSmackRulesAfterUninstall() const
+{
+ const std::vector<AccessRequest> rules(std::move(parseSmackRulesFile(SMACK_RULES_PATH)));
+ const std::string labels[] = {generatePathRWLabel(m_pkgName),
+ generatePathROLabel(m_pkgName),
+ generateProcessLabel(m_appName, m_pkgName, true),
+ generateProcessLabel(m_appName, m_pkgName)};
+
+ for (const auto &rule : rules) {
+ for (const auto &label : labels) {
+ assertNoLabelInRule(rule, label);
+ }
+ }
+}
+
+void AppInstallHelperExt::checkHybridAppSmackRulesAterUninstall() const
+{
+ const std::vector<AccessRequest> rules(parseSmackRulesFile(SMACK_RULES_PATH));
+ const std::string appLabel = generateProcessLabel(m_appName, m_pkgName, true);
+
+ for (const auto &rule : rules) {
+ assertNoLabelInRule(rule, appLabel);
+ }
+}
+
+void AppInstallHelperExt::checkAppIdExistence(bool expected) const
+{
+ lib_retcode ret = expected ? SECURITY_MANAGER_SUCCESS : SECURITY_MANAGER_ERROR_NO_SUCH_OBJECT;
+ std::string retPkgId = Api::getPkgId(m_appName, ret);
+
+ if (expected) {
+ RUNNER_ASSERT_MSG(m_pkgName == retPkgId,
+ "The given appId does not belong to the given pkgId.");
+ }
+}
+
+void AppInstallHelperExt::checkGids(const std::vector<gid_t> &allowedGids) const
+{
+ int ret;
+ std::unordered_set<gid_t> referenceGids(allowedGids.begin(), allowedGids.end());
+
+ // Reset supplementary groups
+ ret = setgroups(0, NULL);
+ RUNNER_ASSERT_MSG(ret != -1, "Unable to set supplementary groups");
+
+ Api::setProcessGroups(m_appName);
+
+ ret = getgroups(0, nullptr);
+ RUNNER_ASSERT_MSG(ret != -1, "Unable to get supplementary groups");
+
+ std::vector<gid_t> actualGids(ret);
+ ret = getgroups(ret, actualGids.data());
+ RUNNER_ASSERT_MSG(ret != -1, "Unable to get supplementary groups");
+
+ for (const auto &gid : actualGids) {
+ RUNNER_ASSERT_MSG(referenceGids.count(gid) > 0,
+ "Application shouldn't get access to group " << gid);
+ referenceGids.erase(gid);
+ }
+
+ RUNNER_ASSERT_MSG(referenceGids.empty(), "Application didn't get access to some groups");
+}
+
+} // namespace SecurityManagerTest