[Windows] Simplify token privilege removal.
This CL simplifies the removal of privileges in a restricted token. The
fixed levels only permit leaving all privileges, removing all but the
traversal privilege or removing all. Therefore we just codify that in
the API.
Bug: 1270309
Change-Id: Ifa8a6a1ba1a4406271bb90af72f38ac6a1d24a93
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3309405
Commit-Queue: James Forshaw <forshaw@chromium.org>
Reviewed-by: Will Harris <wfh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#946753}
NOKEYCHECK=True
GitOrigin-RevId: 6d62640f4bd0731e6c5f1723d5effbf0adce9d6c
diff --git a/win/src/restricted_token.cc b/win/src/restricted_token.cc
index 7977565..544b03d 100644
--- a/win/src/restricted_token.cc
+++ b/win/src/restricted_token.cc
@@ -18,18 +18,6 @@
namespace {
-LUID ConvertToLuid(const CHROME_LUID& luid) {
- LUID ret;
- memcpy(&ret, &luid, sizeof(luid));
- return ret;
-}
-
-CHROME_LUID ConvertToChromeLuid(const LUID& luid) {
- CHROME_LUID ret;
- memcpy(&ret, &luid, sizeof(luid));
- return ret;
-}
-
std::vector<SID_AND_ATTRIBUTES> ConvertToAttributes(
const std::vector<base::win::Sid>& sids,
DWORD attributes) {
@@ -41,6 +29,17 @@
return ret;
}
+bool DeletePrivilege(const base::win::ScopedHandle& token,
+ const wchar_t* name) {
+ TOKEN_PRIVILEGES privs = {};
+ privs.PrivilegeCount = 1;
+ if (!::LookupPrivilegeValue(nullptr, name, &privs.Privileges[0].Luid))
+ return false;
+ privs.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED;
+ return !!::AdjustTokenPrivileges(token.Get(), FALSE, &privs, 0, nullptr,
+ nullptr);
+}
+
} // namespace
namespace sandbox {
@@ -48,7 +47,9 @@
RestrictedToken::RestrictedToken()
: integrity_level_(INTEGRITY_LEVEL_LAST),
init_(false),
- lockdown_default_dacl_(false) {}
+ lockdown_default_dacl_(false),
+ delete_all_privileges_(false),
+ remove_traversal_privilege_(false) {}
RestrictedToken::~RestrictedToken() {}
@@ -92,20 +93,16 @@
ConvertToAttributes(sids_for_deny_only_, SE_GROUP_USE_FOR_DENY_ONLY);
std::vector<SID_AND_ATTRIBUTES> restrict_sids =
ConvertToAttributes(sids_to_restrict_, 0);
- std::vector<LUID_AND_ATTRIBUTES> disable_privs(privileges_to_disable_.size());
- for (size_t i = 0; i < privileges_to_disable_.size(); ++i) {
- disable_privs[i].Attributes = 0;
- disable_privs[i].Luid = ConvertToLuid(privileges_to_disable_[i]);
- }
bool result = true;
HANDLE new_token_handle = nullptr;
- if (!deny_sids.empty() || !restrict_sids.empty() || !disable_privs.empty()) {
+ if (!deny_sids.empty() || !restrict_sids.empty() || delete_all_privileges_) {
result = ::CreateRestrictedToken(
- effective_token_.Get(), 0, static_cast<DWORD>(deny_sids.size()),
- deny_sids.data(), static_cast<DWORD>(disable_privs.size()),
- disable_privs.data(), static_cast<DWORD>(restrict_sids.size()),
- restrict_sids.data(), &new_token_handle);
+ effective_token_.Get(),
+ delete_all_privileges_ ? DISABLE_MAX_PRIVILEGE : 0,
+ static_cast<DWORD>(deny_sids.size()), deny_sids.data(), 0, nullptr,
+ static_cast<DWORD>(restrict_sids.size()), restrict_sids.data(),
+ &new_token_handle);
} else {
// Duplicate the token even if it's not modified at this point
// because any subsequent changes to this token would also affect the
@@ -119,6 +116,10 @@
return ::GetLastError();
base::win::ScopedHandle new_token(new_token_handle);
+ if (delete_all_privileges_ && remove_traversal_privilege_) {
+ if (!DeletePrivilege(new_token, SE_CHANGE_NOTIFY_NAME))
+ return ::GetLastError();
+ }
if (lockdown_default_dacl_) {
// Don't add Restricted sid and also remove logon sid access.
@@ -232,35 +233,12 @@
return ERROR_SUCCESS;
}
-DWORD RestrictedToken::DeleteAllPrivileges(
- const std::vector<std::wstring>& exceptions) {
+DWORD RestrictedToken::DeleteAllPrivileges(bool remove_traversal_privilege) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
- std::unordered_set<std::wstring> privilege_set(exceptions.begin(),
- exceptions.end());
- // Build the list of privileges to disable
- for (const base::win::AccessToken::Privilege& privilege :
- query_token_->Privileges()) {
- if (privilege_set.count(privilege.GetName()) == 0) {
- privileges_to_disable_.push_back(privilege.GetLuid());
- }
- }
-
- return ERROR_SUCCESS;
-}
-
-DWORD RestrictedToken::DeletePrivilege(const wchar_t* privilege) {
- DCHECK(init_);
- if (!init_)
- return ERROR_NO_TOKEN;
-
- LUID luid = {0};
- if (::LookupPrivilegeValue(nullptr, privilege, &luid))
- privileges_to_disable_.push_back(ConvertToChromeLuid(luid));
- else
- return ::GetLastError();
-
+ delete_all_privileges_ = true;
+ remove_traversal_privilege_ = remove_traversal_privilege;
return ERROR_SUCCESS;
}
diff --git a/win/src/restricted_token.h b/win/src/restricted_token.h
index 71ac545..9cb7e1d 100644
--- a/win/src/restricted_token.h
+++ b/win/src/restricted_token.h
@@ -109,32 +109,14 @@
// the error.
DWORD AddUserSidForDenyOnly();
- // Lists all privileges in the token and add them to the list of privileges
- // to remove except for those present in the exceptions parameter. If
- // there is no exception needed, the caller can pass an empty list or nullptr
- // for the exceptions parameter.
+ // Specify to remove all privileges in the restricted token. By default this
+ // will not remove SeChangeNotifyPrivilege, however you can specify true for
+ // |remove_traversal_privilege| to remove that privilege as well.
//
// If the function succeeds, the return value is ERROR_SUCCESS. If the
// function fails, the return value is the win32 error code corresponding to
// the error.
- //
- // Sample usage:
- // std::vector<std::wstring> privilege_exceptions;
- // privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
- // restricted_token.DeleteAllPrivileges(privilege_exceptions);
- DWORD DeleteAllPrivileges(const std::vector<std::wstring>& exceptions);
-
- // Adds a privilege to the list of privileges to remove in the restricted
- // token.
- // Parameter: privilege is the privilege name to remove. This is the string
- // representing the privilege. (e.g. "SeChangeNotifyPrivilege").
- // If the function succeeds, the return value is ERROR_SUCCESS. If the
- // function fails, the return value is the win32 error code corresponding to
- // the error.
- //
- // Sample usage:
- // restricted_token.DeletePrivilege(SE_LOAD_DRIVER_NAME);
- DWORD DeletePrivilege(const wchar_t* privilege);
+ DWORD DeleteAllPrivileges(bool remove_traversal_privilege);
// Adds a SID to the list of restricting sids in the restricted token.
// Parameter: sid is the sid to add to the list restricting sids.
@@ -206,8 +188,6 @@
private:
// The list of restricting sids in the restricted token.
std::vector<base::win::Sid> sids_to_restrict_;
- // The list of privileges to remove in the restricted token.
- std::vector<CHROME_LUID> privileges_to_disable_;
// The list of sids to mark as Deny Only in the restricted token.
std::vector<base::win::Sid> sids_for_deny_only_;
// The list of sids to add to the default DACL of the restricted token.
@@ -223,6 +203,10 @@
bool init_;
// Lockdown the default DACL when creating new tokens.
bool lockdown_default_dacl_;
+ // Delete all privileges except for SeChangeNotifyPrivilege.
+ bool delete_all_privileges_;
+ // Also delete SeChangeNotifyPrivilege if delete_all_privileges_ is true.
+ bool remove_traversal_privilege_;
};
} // namespace sandbox
diff --git a/win/src/restricted_token_unittest.cc b/win/src/restricted_token_unittest.cc
index e0dd7da..3f35043 100644
--- a/win/src/restricted_token_unittest.cc
+++ b/win/src/restricted_token_unittest.cc
@@ -410,7 +410,8 @@
base::win::ScopedHandle token_handle;
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr));
- ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.DeleteAllPrivileges({}));
+ ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
+ token.DeleteAllPrivileges(/*remove_traversal_privilege=*/true));
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
token.GetRestrictedToken(&token_handle));
auto restricted_token = base::win::AccessToken::FromToken(token_handle.Get());
@@ -423,12 +424,9 @@
RestrictedToken token;
base::win::ScopedHandle token_handle;
- std::vector<std::wstring> exceptions;
- exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
-
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr));
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
- token.DeleteAllPrivileges(exceptions));
+ token.DeleteAllPrivileges(/*remove_traversal_privilege=*/false));
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
token.GetRestrictedToken(&token_handle));
@@ -439,23 +437,6 @@
EXPECT_EQ(privileges[0].GetName(), SE_CHANGE_NOTIFY_NAME);
}
-// Tests the method DeletePrivilege.
-TEST(RestrictedTokenTest, DeletePrivilege) {
- RestrictedToken token;
- base::win::ScopedHandle token_handle;
-
- ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr));
- ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
- token.DeletePrivilege(SE_CHANGE_NOTIFY_NAME));
- ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
- token.GetRestrictedToken(&token_handle));
- auto restricted_token = base::win::AccessToken::FromToken(token_handle.Get());
- ASSERT_TRUE(restricted_token);
- for (const auto& priv : restricted_token->Privileges()) {
- ASSERT_NE(priv.GetName(), SE_CHANGE_NOTIFY_NAME);
- }
-}
-
// Tests the method AddRestrictingSid.
TEST(RestrictedTokenTest, AddRestrictingSid) {
RestrictedToken token;
diff --git a/win/src/restricted_token_utils.cc b/win/src/restricted_token_utils.cc
index f68fe1d..3185f4e 100644
--- a/win/src/restricted_token_utils.cc
+++ b/win/src/restricted_token_utils.cc
@@ -82,6 +82,7 @@
bool deny_sids = true;
bool remove_privileges = true;
+ bool remove_traverse_privilege = false;
switch (security_level) {
case USER_UNPROTECTED: {
@@ -105,7 +106,6 @@
AddSidException(sid_exceptions, base::win::WellKnownSid::kInteractive);
AddSidException(sid_exceptions,
base::win::WellKnownSid::kAuthenticatedUser);
- privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
break;
}
case USER_RESTRICTED_NON_ADMIN: {
@@ -114,7 +114,6 @@
AddSidException(sid_exceptions, base::win::WellKnownSid::kInteractive);
AddSidException(sid_exceptions,
base::win::WellKnownSid::kAuthenticatedUser);
- privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
restricted_token.AddRestrictingSid(
base::win::WellKnownSid::kBuiltinUsers);
restricted_token.AddRestrictingSid(base::win::WellKnownSid::kWorld);
@@ -134,7 +133,6 @@
AddSidException(sid_exceptions, base::win::WellKnownSid::kInteractive);
AddSidException(sid_exceptions,
base::win::WellKnownSid::kAuthenticatedUser);
- privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
restricted_token.AddRestrictingSid(
base::win::WellKnownSid::kBuiltinUsers);
restricted_token.AddRestrictingSid(base::win::WellKnownSid::kWorld);
@@ -149,7 +147,6 @@
AddSidException(sid_exceptions, base::win::WellKnownSid::kBuiltinUsers);
AddSidException(sid_exceptions, base::win::WellKnownSid::kWorld);
AddSidException(sid_exceptions, base::win::WellKnownSid::kInteractive);
- privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
restricted_token.AddRestrictingSid(
base::win::WellKnownSid::kBuiltinUsers);
restricted_token.AddRestrictingSid(base::win::WellKnownSid::kWorld);
@@ -166,7 +163,6 @@
break;
}
case USER_RESTRICTED: {
- privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
restricted_token.AddUserSidForDenyOnly();
restricted_token.AddRestrictingSid(base::win::WellKnownSid::kRestricted);
if (unique_restricted_sid)
@@ -174,6 +170,7 @@
break;
}
case USER_LOCKDOWN: {
+ remove_traverse_privilege = true;
restricted_token.AddUserSidForDenyOnly();
restricted_token.AddRestrictingSid(base::win::WellKnownSid::kNull);
if (unique_restricted_sid)
@@ -191,7 +188,7 @@
}
if (remove_privileges) {
- err_code = restricted_token.DeleteAllPrivileges(privilege_exceptions);
+ err_code = restricted_token.DeleteAllPrivileges(remove_traverse_privilege);
if (ERROR_SUCCESS != err_code)
return err_code;
}