From: Michael Müller michael@fds-team.de
From: Michael Müller michael@fds-team.de Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com --- dlls/ntdll/nt.c | 59 ++++++++++++++++++++++++++++++ dlls/ntdll/ntdll.spec | 2 +- include/winnt.h | 5 +++ include/winternl.h | 1 + server/process.c | 2 +- server/protocol.def | 10 ++++++ server/security.h | 4 ++- server/token.c | 84 +++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 162 insertions(+), 5 deletions(-)
diff --git a/dlls/ntdll/nt.c b/dlls/ntdll/nt.c index f429349698b..8df3543d469 100644 --- a/dlls/ntdll/nt.c +++ b/dlls/ntdll/nt.c @@ -178,6 +178,65 @@ NTSTATUS WINAPI NtDuplicateToken( return status; }
+/****************************************************************************** + * NtFilterToken [NTDLL.@] + * ZwFilterToken [NTDLL.@] + */ +NTSTATUS WINAPI NtFilterToken( HANDLE token, ULONG flags, TOKEN_GROUPS *disable_sids, + TOKEN_PRIVILEGES *privileges, TOKEN_GROUPS *restrict_sids, + HANDLE *new_token ) +{ + data_size_t privileges_len = 0; + data_size_t sids_len = 0; + SID *sids = NULL; + NTSTATUS status; + + TRACE( "(%p, 0x%08x, %p, %p, %p, %p)\n", token, flags, disable_sids, privileges, + restrict_sids, new_token ); + + if (flags) + FIXME( "flags %x unsupported\n", flags ); + + if (restrict_sids) + FIXME( "support for restricting sids not yet implemented\n" ); + + if (privileges) + privileges_len = privileges->PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES); + + if (disable_sids) + { + DWORD len, i; + BYTE *tmp; + + for (i = 0; i < disable_sids->GroupCount; i++) + sids_len += RtlLengthSid( disable_sids->Groups[i].Sid ); + + sids = RtlAllocateHeap( GetProcessHeap(), 0, sids_len ); + if (!sids) return STATUS_NO_MEMORY; + + for (i = 0, tmp = (BYTE *)sids; i < disable_sids->GroupCount; i++, tmp += len) + { + len = RtlLengthSid( disable_sids->Groups[i].Sid ); + memcpy( tmp, disable_sids->Groups[i].Sid, len ); + } + } + + SERVER_START_REQ( filter_token ) + { + req->handle = wine_server_obj_handle( token ); + req->flags = flags; + req->privileges_size = privileges_len; + wine_server_add_data( req, privileges->Privileges, privileges_len ); + wine_server_add_data( req, sids, sids_len ); + status = wine_server_call( req ); + if (!status) *new_token = wine_server_ptr_handle( reply->new_handle ); + } + SERVER_END_REQ; + + RtlFreeHeap( GetProcessHeap(), 0, sids ); + return status; +} + /****************************************************************************** * NtOpenProcessToken [NTDLL.@] * ZwOpenProcessToken [NTDLL.@] diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 292b0f6a9f1..d625c3fde9f 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -180,7 +180,7 @@ # @ stub NtEnumerateSystemEnvironmentValuesEx @ stdcall NtEnumerateValueKey(long long long ptr long ptr) @ stub NtExtendSection -# @ stub NtFilterToken +@ stdcall NtFilterToken(long long ptr ptr ptr ptr) @ stdcall NtFindAtom(ptr long ptr) @ stdcall NtFlushBuffersFile(long ptr) @ stdcall NtFlushInstructionCache(long ptr long) diff --git a/include/winnt.h b/include/winnt.h index bdcd90a9ddc..88da1496cfa 100644 --- a/include/winnt.h +++ b/include/winnt.h @@ -4066,6 +4066,11 @@ typedef enum _TOKEN_INFORMATION_CLASS { TOKEN_ADJUST_SESSIONID | \ TOKEN_ADJUST_DEFAULT )
+#define DISABLE_MAX_PRIVILEGE 0x1 +#define SANDBOX_INERT 0x2 +#define LUA_TOKEN 0x4 +#define WRITE_RESTRICTED 0x8 + #ifndef _SECURITY_DEFINED #define _SECURITY_DEFINED
diff --git a/include/winternl.h b/include/winternl.h index 2b3fb947b9b..183f6d9d031 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -2368,6 +2368,7 @@ NTSYSAPI NTSTATUS WINAPI NtDuplicateToken(HANDLE,ACCESS_MASK,POBJECT_ATTRIBUTES NTSYSAPI NTSTATUS WINAPI NtEnumerateKey(HANDLE,ULONG,KEY_INFORMATION_CLASS,void *,DWORD,DWORD *); NTSYSAPI NTSTATUS WINAPI NtEnumerateValueKey(HANDLE,ULONG,KEY_VALUE_INFORMATION_CLASS,PVOID,ULONG,PULONG); NTSYSAPI NTSTATUS WINAPI NtExtendSection(HANDLE,PLARGE_INTEGER); +NTSYSAPI NTSTATUS WINAPI NtFilterToken(HANDLE,ULONG,TOKEN_GROUPS*,TOKEN_PRIVILEGES*,TOKEN_GROUPS*,HANDLE*); NTSYSAPI NTSTATUS WINAPI NtFindAtom(const WCHAR*,ULONG,RTL_ATOM*); NTSYSAPI NTSTATUS WINAPI NtFlushBuffersFile(HANDLE,IO_STATUS_BLOCK*); NTSYSAPI NTSTATUS WINAPI NtFlushInstructionCache(HANDLE,LPCVOID,SIZE_T); diff --git a/server/process.c b/server/process.c index 473d3b1a27a..e062933766c 100644 --- a/server/process.c +++ b/server/process.c @@ -564,7 +564,7 @@ struct process *create_process( int fd, struct process *parent, int inherit_all, : alloc_handle_table( process, 0 ); /* Note: for security reasons, starting a new process does not attempt * to use the current impersonation token for the new process */ - process->token = token_duplicate( parent->token, TRUE, 0, NULL ); + process->token = token_duplicate( parent->token, TRUE, 0, NULL, NULL, 0, NULL, 0 ); process->affinity = parent->affinity; } if (!process->handles || !process->token) goto error; diff --git a/server/protocol.def b/server/protocol.def index b6ad514463f..18bfa9bf09a 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3388,6 +3388,16 @@ enum caret_state obj_handle_t new_handle; /* duplicated handle */ @END
+@REQ(filter_token) + obj_handle_t handle; /* handle to the token to duplicate */ + unsigned int flags; /* flags */ + data_size_t privileges_size; /* size of privileges */ + VARARG(privileges,LUID_AND_ATTRIBUTES,privileges_size); /* privileges to remove from new token */ + VARARG(disable_sids,SID); /* array of groups to remove from new token */ +@REPLY + obj_handle_t new_handle; /* filtered handle */ +@END + @REQ(access_check) obj_handle_t handle; /* handle to the token */ unsigned int desired_access; /* desired access to the object */ diff --git a/server/security.h b/server/security.h index 873bbc6afd6..bc4a8f64daa 100644 --- a/server/security.h +++ b/server/security.h @@ -55,7 +55,9 @@ extern const PSID security_high_label_sid; extern struct token *token_create_admin(void); extern int token_assign_label( struct token *token, PSID label ); extern struct token *token_duplicate( struct token *src_token, unsigned primary, - int impersonation_level, const struct security_descriptor *sd ); + int impersonation_level, const struct security_descriptor *sd, + const LUID_AND_ATTRIBUTES *filter_privileges, unsigned int priv_count, + const SID *filter_groups, unsigned int group_count ); extern int token_check_privileges( struct token *token, int all_required, const LUID_AND_ATTRIBUTES *reqprivs, unsigned int count, LUID_AND_ATTRIBUTES *usedprivs); diff --git a/server/token.c b/server/token.c index 5c35bcc19f6..de76939831e 100644 --- a/server/token.c +++ b/server/token.c @@ -285,6 +285,19 @@ static int acl_is_valid( const ACL *acl, data_size_t size ) return TRUE; }
+static unsigned int get_sid_count( const SID *sid, data_size_t size ) +{ + unsigned int count; + + for (count = 0; size >= sizeof(SID) && security_sid_len( sid ) <= size; count++) + { + size -= security_sid_len( sid ); + sid = (const SID *)((char *)sid + security_sid_len( sid )); + } + + return count; +} + /* checks whether all members of a security descriptor fit inside the size * of memory specified */ int sd_is_valid( const struct security_descriptor *sd, data_size_t size ) @@ -626,8 +639,36 @@ static struct token *create_token( unsigned primary, const SID *user, return token; }
+static int filter_group( struct group *group, const SID *filter, unsigned int count ) +{ + unsigned int i; + + for (i = 0; i < count; i++) + { + if (security_equal_sid( &group->sid, filter )) return 1; + filter = (const SID *)((char *)filter + security_sid_len( filter )); + } + + return 0; +} + +static int filter_privilege( struct privilege *privilege, const LUID_AND_ATTRIBUTES *filter, unsigned int count ) +{ + unsigned int i; + + for (i = 0; i < count; i++) + { + if (!memcmp( &privilege->luid, &filter[i].Luid, sizeof(LUID) )) + return 1; + } + + return 0; +} + struct token *token_duplicate( struct token *src_token, unsigned primary, - int impersonation_level, const struct security_descriptor *sd ) + int impersonation_level, const struct security_descriptor *sd, + const LUID_AND_ATTRIBUTES *filter_privileges, unsigned int priv_count, + const SID *filter_groups, unsigned int group_count) { const luid_t *modified_id = primary || (impersonation_level == src_token->impersonation_level) ? @@ -663,6 +704,12 @@ struct token *token_duplicate( struct token *src_token, unsigned primary, return NULL; } memcpy( newgroup, group, size ); + if (filter_group( group, filter_groups, group_count )) + { + newgroup->enabled = 0; + newgroup->def = 0; + newgroup->deny_only = 1; + } list_add_tail( &token->groups, &newgroup->entry ); if (src_token->primary_group == &group->sid) { @@ -674,11 +721,14 @@ struct token *token_duplicate( struct token *src_token, unsigned primary,
/* copy privileges */ LIST_FOR_EACH_ENTRY( privilege, &src_token->privileges, struct privilege, entry ) + { + if (filter_privilege( privilege, filter_privileges, priv_count )) continue; if (!privilege_add( token, &privilege->luid, privilege->enabled )) { release_object( token ); return NULL; } + }
if (sd) default_set_sd( &token->obj, sd, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION ); @@ -1310,7 +1360,7 @@ DECL_HANDLER(duplicate_token) TOKEN_DUPLICATE, &token_ops ))) { - struct token *token = token_duplicate( src_token, req->primary, req->impersonation_level, sd ); + struct token *token = token_duplicate( src_token, req->primary, req->impersonation_level, sd, NULL, 0, NULL, 0 ); if (token) { reply->new_handle = alloc_handle_no_access_check( current->process, token, req->access, objattr->attributes ); @@ -1320,6 +1370,36 @@ DECL_HANDLER(duplicate_token) } }
+/* creates a restricted version of a token */ +DECL_HANDLER(filter_token) +{ + struct token *src_token; + + if ((src_token = (struct token *)get_handle_obj( current->process, req->handle, + TOKEN_DUPLICATE, + &token_ops ))) + { + const LUID_AND_ATTRIBUTES *filter_privileges = get_req_data(); + unsigned int priv_count, group_count; + const SID *filter_groups; + struct token *token; + + priv_count = min( req->privileges_size, get_req_data_size() ) / sizeof(LUID_AND_ATTRIBUTES); + filter_groups = (const SID *)((char *)filter_privileges + priv_count * sizeof(LUID_AND_ATTRIBUTES)); + group_count = get_sid_count( filter_groups, get_req_data_size() - priv_count * sizeof(LUID_AND_ATTRIBUTES) ); + + token = token_duplicate( src_token, src_token->primary, src_token->impersonation_level, NULL, + filter_privileges, priv_count, filter_groups, group_count ); + if (token) + { + unsigned int access = get_handle_access( current->process, req->handle ); + reply->new_handle = alloc_handle_no_access_check( current->process, token, access, 0 ); + release_object( token ); + } + release_object( src_token ); + } +} + /* checks the specified privileges are held by the token */ DECL_HANDLER(check_token_privileges) {
From: Michael Müller michael@fds-team.de
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=25834 From: Michael Müller michael@fds-team.de Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com --- dlls/advapi32/security.c | 88 +++++++++++++++++++++++++++++----- dlls/advapi32/tests/security.c | 88 +++++++++++++++++++++++++++++++--- 2 files changed, 157 insertions(+), 19 deletions(-)
diff --git a/dlls/advapi32/security.c b/dlls/advapi32/security.c index 6891feea2a0..c1c5d05d6a4 100644 --- a/dlls/advapi32/security.c +++ b/dlls/advapi32/security.c @@ -832,6 +832,60 @@ BOOL WINAPI SetThreadToken(PHANDLE thread, HANDLE token) ThreadImpersonationToken, &token, sizeof token )); }
+static BOOL allocate_groups(TOKEN_GROUPS **groups_ret, SID_AND_ATTRIBUTES *sids, DWORD count) +{ + TOKEN_GROUPS *groups; + DWORD i; + + if (!count) + { + *groups_ret = NULL; + return TRUE; + } + + groups = (TOKEN_GROUPS *)heap_alloc(FIELD_OFFSET(TOKEN_GROUPS, Groups) + + count * sizeof(SID_AND_ATTRIBUTES)); + if (!groups) + { + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + + groups->GroupCount = count; + for (i = 0; i < count; i++) + groups->Groups[i] = sids[i]; + + *groups_ret = groups; + return TRUE; +} + +static BOOL allocate_privileges(TOKEN_PRIVILEGES **privileges_ret, LUID_AND_ATTRIBUTES *privs, DWORD count) +{ + TOKEN_PRIVILEGES *privileges; + DWORD i; + + if (!count) + { + *privileges_ret = NULL; + return TRUE; + } + + privileges = (TOKEN_PRIVILEGES *)heap_alloc(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + + count * sizeof(LUID_AND_ATTRIBUTES)); + if (!privileges) + { + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + + privileges->PrivilegeCount = count; + for (i = 0; i < count; i++) + privileges->Privileges[i] = privs[i]; + + *privileges_ret = privileges; + return TRUE; +} + /************************************************************************* * CreateRestrictedToken [ADVAPI32.@] * @@ -863,25 +917,33 @@ BOOL WINAPI CreateRestrictedToken( PSID_AND_ATTRIBUTES restrictSids, PHANDLE newToken) { - TOKEN_TYPE type; - SECURITY_IMPERSONATION_LEVEL level = SecurityAnonymous; - DWORD size; + TOKEN_PRIVILEGES *delete_privs = NULL; + TOKEN_GROUPS *disable_groups = NULL; + TOKEN_GROUPS *restrict_sids = NULL; + BOOL ret = FALSE;
- FIXME("(%p, 0x%x, %u, %p, %u, %p, %u, %p, %p): stub\n", + TRACE("(%p, 0x%x, %u, %p, %u, %p, %u, %p, %p)\n", baseToken, flags, nDisableSids, disableSids, nDeletePrivs, deletePrivs, nRestrictSids, restrictSids, newToken);
- size = sizeof(type); - if (!GetTokenInformation( baseToken, TokenType, &type, size, &size )) return FALSE; - if (type == TokenImpersonation) - { - size = sizeof(level); - if (!GetTokenInformation( baseToken, TokenImpersonationLevel, &level, size, &size )) - return FALSE; - } - return DuplicateTokenEx( baseToken, MAXIMUM_ALLOWED, NULL, level, type, newToken ); + if (!allocate_groups(&disable_groups, disableSids, nDisableSids)) + goto done; + + if (!allocate_privileges(&delete_privs, deletePrivs, nDeletePrivs)) + goto done; + + if (!allocate_groups(&restrict_sids, restrictSids, nRestrictSids)) + goto done; + + ret = set_ntstatus(NtFilterToken(baseToken, flags, disable_groups, delete_privs, restrict_sids, newToken)); + +done: + heap_free(disable_groups); + heap_free(delete_privs); + heap_free(restrict_sids); + return ret; }
/* ############################## diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c index d9cae64da8b..62aa556846a 100644 --- a/dlls/advapi32/tests/security.c +++ b/dlls/advapi32/tests/security.c @@ -5120,10 +5120,13 @@ static void test_GetUserNameW(void)
static void test_CreateRestrictedToken(void) { + TOKEN_PRIMARY_GROUP *primary_group, *primary_group2; HANDLE process_token, token, r_token; PTOKEN_GROUPS token_groups, groups2; SID_AND_ATTRIBUTES sattr; SECURITY_IMPERSONATION_LEVEL level; + TOKEN_PRIVILEGES *privs; + PRIVILEGE_SET privset; TOKEN_TYPE type; BOOL is_member; DWORD size; @@ -5139,7 +5142,7 @@ static void test_CreateRestrictedToken(void) ret = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, &process_token); ok(ret, "got error %d\n", GetLastError());
- ret = DuplicateTokenEx(process_token, TOKEN_DUPLICATE|TOKEN_ADJUST_GROUPS|TOKEN_QUERY, + ret = DuplicateTokenEx(process_token, TOKEN_DUPLICATE|TOKEN_ADJUST_GROUPS|TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, NULL, SecurityImpersonation, TokenImpersonation, &token); ok(ret, "got error %d\n", GetLastError());
@@ -5170,11 +5173,21 @@ static void test_CreateRestrictedToken(void) ok(ret, "got error %d\n", GetLastError()); ok(is_member, "not a member\n");
- /* disable a SID in new token */ + privset.PrivilegeCount = 1; + privset.Control = PRIVILEGE_SET_ALL_NECESSARY; + ret = LookupPrivilegeValueA(NULL, "SeChangeNotifyPrivilege", &privset.Privilege[0].Luid); + ok(ret, "got error %d\n", GetLastError()); + + is_member = FALSE; + ret = PrivilegeCheck(token, &privset, &is_member); + ok(ret, "got error %d\n", GetLastError()); + ok(is_member, "Expected SeChangeNotifyPrivilege to be enabled\n"); + + /* disable a SID and a privilege in new token */ sattr.Sid = token_groups->Groups[i].Sid; sattr.Attributes = 0; r_token = NULL; - ret = pCreateRestrictedToken(token, 0, 1, &sattr, 0, NULL, 0, NULL, &r_token); + ret = pCreateRestrictedToken(token, 0, 1, &sattr, 1, &privset.Privilege[0], 0, NULL, &r_token); ok(ret, "got error %d\n", GetLastError());
if (ret) @@ -5183,7 +5196,7 @@ static void test_CreateRestrictedToken(void) is_member = TRUE; ret = pCheckTokenMembership(r_token, token_groups->Groups[i].Sid, &is_member); ok(ret, "got error %d\n", GetLastError()); - todo_wine ok(!is_member, "not a member\n"); + ok(!is_member, "not a member\n");
ret = GetTokenInformation(r_token, TokenGroups, NULL, 0, &size); ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n", @@ -5198,9 +5211,9 @@ static void test_CreateRestrictedToken(void) break; }
- todo_wine ok(groups2->Groups[j].Attributes & SE_GROUP_USE_FOR_DENY_ONLY, + ok(groups2->Groups[j].Attributes & SE_GROUP_USE_FOR_DENY_ONLY, "got wrong attributes\n"); - todo_wine ok((groups2->Groups[j].Attributes & SE_GROUP_ENABLED) == 0, + ok((groups2->Groups[j].Attributes & SE_GROUP_ENABLED) == 0, "got wrong attributes\n");
HeapFree(GetProcessHeap(), 0, groups2); @@ -5214,10 +5227,73 @@ static void test_CreateRestrictedToken(void) ret = GetTokenInformation(r_token, TokenImpersonationLevel, &level, size, &size); ok(ret, "got error %d\n", GetLastError()); ok(level == SecurityImpersonation, "got level %u\n", type); + + is_member = TRUE; + ret = PrivilegeCheck(r_token, &privset, &is_member); + ok(ret, "got error %d\n", GetLastError()); + ok(!is_member, "Expected SeChangeNotifyPrivilege not to be enabled\n"); + + ret = GetTokenInformation(r_token, TokenPrivileges, NULL, 0, &size); + ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n", + ret, GetLastError()); + privs = HeapAlloc(GetProcessHeap(), 0, size); + ret = GetTokenInformation(r_token, TokenPrivileges, privs, size, &size); + ok(ret, "got error %d\n", GetLastError()); + + is_member = FALSE; + for (j = 0; j < privs->PrivilegeCount; j++) + { + if (RtlEqualLuid(&privs->Privileges[j].Luid, &privset.Privilege[0].Luid)) + { + is_member = TRUE; + break; + } + } + + ok(!is_member, "Expected not to find privilege\n"); + HeapFree(GetProcessHeap(), 0, privs); }
HeapFree(GetProcessHeap(), 0, token_groups); CloseHandle(r_token); + + ret = GetTokenInformation(token, TokenPrimaryGroup, NULL, 0, &size); + ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n", + ret, GetLastError()); + primary_group = HeapAlloc(GetProcessHeap(), 0, size); + ret = GetTokenInformation(token, TokenPrimaryGroup, primary_group, size, &size); + ok(ret, "got error %d\n", GetLastError()); + + /* disable primary group */ + sattr.Sid = primary_group->PrimaryGroup; + sattr.Attributes = 0; + r_token = NULL; + ret = pCreateRestrictedToken(token, 0, 1, &sattr, 0, NULL, 0, NULL, &r_token); + ok(ret, "got error %d\n", GetLastError()); + + if (ret) + { + is_member = TRUE; + ret = pCheckTokenMembership(r_token, primary_group->PrimaryGroup, &is_member); + ok(ret, "got error %d\n", GetLastError()); + ok(!is_member, "not a member\n"); + + ret = GetTokenInformation(r_token, TokenPrimaryGroup, NULL, 0, &size); + ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n", + ret, GetLastError()); + primary_group2 = HeapAlloc(GetProcessHeap(), 0, size); + ret = GetTokenInformation(r_token, TokenPrimaryGroup, primary_group2, size, &size); + ok(ret, "got error %d\n", GetLastError()); + + ok(EqualSid(primary_group2->PrimaryGroup, primary_group->PrimaryGroup), + "Expected same primary group\n"); + + HeapFree(GetProcessHeap(), 0, primary_group2); + } + + HeapFree(GetProcessHeap(), 0, primary_group); + CloseHandle(r_token); + CloseHandle(token); CloseHandle(process_token); }