blob: 7ebef3de9aefd278df5a012c95c9cc220e3983dd [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sandbox/win/src/restricted_token.h"
#include <vector>
#include "base/logging.h"
#include "sandbox/win/src/acl.h"
#include "sandbox/win/src/win_utils.h"
namespace sandbox {
RestrictedToken::RestrictedToken()
: init_(false),
effective_token_(NULL),
integrity_level_(INTEGRITY_LEVEL_LAST) {
}
RestrictedToken::~RestrictedToken() {
if (effective_token_)
CloseHandle(effective_token_);
}
unsigned RestrictedToken::Init(const HANDLE effective_token) {
if (init_)
return ERROR_ALREADY_INITIALIZED;
if (effective_token) {
// We duplicate the handle to be able to use it even if the original handle
// is closed.
HANDLE effective_token_dup;
if (::DuplicateHandle(::GetCurrentProcess(),
effective_token,
::GetCurrentProcess(),
&effective_token_dup,
0,
FALSE,
DUPLICATE_SAME_ACCESS)) {
effective_token_ = effective_token_dup;
} else {
return ::GetLastError();
}
} else {
if (!::OpenProcessToken(::GetCurrentProcess(),
TOKEN_ALL_ACCESS,
&effective_token_))
return ::GetLastError();
}
init_ = true;
return ERROR_SUCCESS;
}
unsigned RestrictedToken::GetRestrictedTokenHandle(HANDLE *token_handle) const {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
size_t deny_size = sids_for_deny_only_.size();
size_t restrict_size = sids_to_restrict_.size();
size_t privileges_size = privileges_to_disable_.size();
SID_AND_ATTRIBUTES *deny_only_array = NULL;
if (deny_size) {
deny_only_array = new SID_AND_ATTRIBUTES[deny_size];
for (unsigned int i = 0; i < sids_for_deny_only_.size() ; ++i) {
deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY;
deny_only_array[i].Sid =
const_cast<SID*>(sids_for_deny_only_[i].GetPSID());
}
}
SID_AND_ATTRIBUTES *sids_to_restrict_array = NULL;
if (restrict_size) {
sids_to_restrict_array = new SID_AND_ATTRIBUTES[restrict_size];
for (unsigned int i = 0; i < restrict_size; ++i) {
sids_to_restrict_array[i].Attributes = 0;
sids_to_restrict_array[i].Sid =
const_cast<SID*>(sids_to_restrict_[i].GetPSID());
}
}
LUID_AND_ATTRIBUTES *privileges_to_disable_array = NULL;
if (privileges_size) {
privileges_to_disable_array = new LUID_AND_ATTRIBUTES[privileges_size];
for (unsigned int i = 0; i < privileges_size; ++i) {
privileges_to_disable_array[i].Attributes = 0;
privileges_to_disable_array[i].Luid = privileges_to_disable_[i];
}
}
BOOL result = TRUE;
HANDLE new_token = NULL;
// The SANDBOX_INERT flag did nothing in XP and it was just a way to tell
// if a token has ben restricted given the limiations of IsTokenRestricted()
// but it appears that in Windows 7 it hints the AppLocker subsystem to
// leave us alone.
if (deny_size || restrict_size || privileges_size) {
result = ::CreateRestrictedToken(effective_token_,
SANDBOX_INERT,
static_cast<DWORD>(deny_size),
deny_only_array,
static_cast<DWORD>(privileges_size),
privileges_to_disable_array,
static_cast<DWORD>(restrict_size),
sids_to_restrict_array,
&new_token);
} else {
// Duplicate the token even if it's not modified at this point
// because any subsequent changes to this token would also affect the
// current process.
result = ::DuplicateTokenEx(effective_token_, TOKEN_ALL_ACCESS, NULL,
SecurityIdentification, TokenPrimary,
&new_token);
}
if (deny_only_array)
delete[] deny_only_array;
if (sids_to_restrict_array)
delete[] sids_to_restrict_array;
if (privileges_to_disable_array)
delete[] privileges_to_disable_array;
if (!result)
return ::GetLastError();
// Modify the default dacl on the token to contain Restricted and the user.
if (!AddSidToDefaultDacl(new_token, WinRestrictedCodeSid, GENERIC_ALL))
return ::GetLastError();
if (!AddUserSidToDefaultDacl(new_token, GENERIC_ALL))
return ::GetLastError();
DWORD error = SetTokenIntegrityLevel(new_token, integrity_level_);
if (ERROR_SUCCESS != error)
return error;
BOOL status = ::DuplicateHandle(::GetCurrentProcess(),
new_token,
::GetCurrentProcess(),
token_handle,
TOKEN_ALL_ACCESS,
FALSE, // Don't inherit.
0);
if (new_token != effective_token_)
::CloseHandle(new_token);
if (!status)
return ::GetLastError();
return ERROR_SUCCESS;
}
unsigned RestrictedToken::GetRestrictedTokenHandleForImpersonation(
HANDLE *token_handle) const {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
HANDLE restricted_token_handle;
unsigned err_code = GetRestrictedTokenHandle(&restricted_token_handle);
if (ERROR_SUCCESS != err_code)
return err_code;
HANDLE impersonation_token;
if (!::DuplicateToken(restricted_token_handle,
SecurityImpersonation,
&impersonation_token)) {
::CloseHandle(restricted_token_handle);
return ::GetLastError();
}
::CloseHandle(restricted_token_handle);
BOOL status = ::DuplicateHandle(::GetCurrentProcess(),
impersonation_token,
::GetCurrentProcess(),
token_handle,
TOKEN_ALL_ACCESS,
FALSE, // Don't inherit.
0);
::CloseHandle(impersonation_token);
if (!status)
return ::GetLastError();
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddAllSidsForDenyOnly(std::vector<Sid> *exceptions) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
TOKEN_GROUPS *token_groups = NULL;
DWORD size = 0;
BOOL result = ::GetTokenInformation(effective_token_,
TokenGroups,
NULL, // No buffer.
0, // Size is 0.
&size);
if (!size)
return ::GetLastError();
token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]);
result = ::GetTokenInformation(effective_token_,
TokenGroups,
token_groups,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE*>(token_groups);
return ::GetLastError();
}
// Build the list of the deny only group SIDs
for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) {
if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 &&
(token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0) {
bool should_ignore = false;
if (exceptions) {
for (unsigned int j = 0; j < exceptions->size(); ++j) {
if (::EqualSid(const_cast<SID*>((*exceptions)[j].GetPSID()),
token_groups->Groups[i].Sid)) {
should_ignore = true;
break;
}
}
}
if (!should_ignore) {
sids_for_deny_only_.push_back(
reinterpret_cast<SID*>(token_groups->Groups[i].Sid));
}
}
}
delete[] reinterpret_cast<BYTE*>(token_groups);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddSidForDenyOnly(const Sid &sid) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
sids_for_deny_only_.push_back(sid);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddUserSidForDenyOnly() {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE;
TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(new BYTE[size]);
BOOL result = ::GetTokenInformation(effective_token_,
TokenUser,
token_user,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE*>(token_user);
return ::GetLastError();
}
Sid user = reinterpret_cast<SID*>(token_user->User.Sid);
sids_for_deny_only_.push_back(user);
delete[] reinterpret_cast<BYTE*>(token_user);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::DeleteAllPrivileges(
const std::vector<base::string16> *exceptions) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
// Get the list of privileges in the token
TOKEN_PRIVILEGES *token_privileges = NULL;
DWORD size = 0;
BOOL result = ::GetTokenInformation(effective_token_,
TokenPrivileges,
NULL, // No buffer.
0, // Size is 0.
&size);
if (!size)
return ::GetLastError();
token_privileges = reinterpret_cast<TOKEN_PRIVILEGES*>(new BYTE[size]);
result = ::GetTokenInformation(effective_token_,
TokenPrivileges,
token_privileges,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE *>(token_privileges);
return ::GetLastError();
}
// Build the list of privileges to disable
for (unsigned int i = 0; i < token_privileges->PrivilegeCount; ++i) {
bool should_ignore = false;
if (exceptions) {
for (unsigned int j = 0; j < exceptions->size(); ++j) {
LUID luid = {0};
::LookupPrivilegeValue(NULL, (*exceptions)[j].c_str(), &luid);
if (token_privileges->Privileges[i].Luid.HighPart == luid.HighPart &&
token_privileges->Privileges[i].Luid.LowPart == luid.LowPart) {
should_ignore = true;
break;
}
}
}
if (!should_ignore) {
privileges_to_disable_.push_back(token_privileges->Privileges[i].Luid);
}
}
delete[] reinterpret_cast<BYTE *>(token_privileges);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::DeletePrivilege(const wchar_t *privilege) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
LUID luid = {0};
if (LookupPrivilegeValue(NULL, privilege, &luid))
privileges_to_disable_.push_back(luid);
else
return ::GetLastError();
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddRestrictingSid(const Sid &sid) {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
sids_to_restrict_.push_back(sid); // No attributes
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddRestrictingSidLogonSession() {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
TOKEN_GROUPS *token_groups = NULL;
DWORD size = 0;
BOOL result = ::GetTokenInformation(effective_token_,
TokenGroups,
NULL, // No buffer.
0, // Size is 0.
&size);
if (!size)
return ::GetLastError();
token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]);
result = ::GetTokenInformation(effective_token_,
TokenGroups,
token_groups,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE*>(token_groups);
return ::GetLastError();
}
SID *logon_sid = NULL;
for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) {
if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) {
logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid);
break;
}
}
if (logon_sid)
sids_to_restrict_.push_back(logon_sid);
delete[] reinterpret_cast<BYTE*>(token_groups);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddRestrictingSidCurrentUser() {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE;
TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(new BYTE[size]);
BOOL result = ::GetTokenInformation(effective_token_,
TokenUser,
token_user,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE*>(token_user);
return ::GetLastError();
}
Sid user = reinterpret_cast<SID*>(token_user->User.Sid);
sids_to_restrict_.push_back(user);
delete[] reinterpret_cast<BYTE*>(token_user);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::AddRestrictingSidAllSids() {
DCHECK(init_);
if (!init_)
return ERROR_NO_TOKEN;
// Add the current user to the list.
unsigned error = AddRestrictingSidCurrentUser();
if (ERROR_SUCCESS != error)
return error;
TOKEN_GROUPS *token_groups = NULL;
DWORD size = 0;
// Get the buffer size required.
BOOL result = ::GetTokenInformation(effective_token_, TokenGroups, NULL, 0,
&size);
if (!size)
return ::GetLastError();
token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]);
result = ::GetTokenInformation(effective_token_,
TokenGroups,
token_groups,
size,
&size);
if (!result) {
delete[] reinterpret_cast<BYTE*>(token_groups);
return ::GetLastError();
}
// Build the list of restricting sids from all groups.
for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) {
if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0)
AddRestrictingSid(reinterpret_cast<SID*>(token_groups->Groups[i].Sid));
}
delete[] reinterpret_cast<BYTE*>(token_groups);
return ERROR_SUCCESS;
}
unsigned RestrictedToken::SetIntegrityLevel(IntegrityLevel integrity_level) {
integrity_level_ = integrity_level;
return ERROR_SUCCESS;
}
} // namespace sandbox