v2: Validate request data.
Signed-off-by: Hans Leidekker hans@codeweavers.com --- dlls/ntdll/nt.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/ntdll.spec | 2 +- server/protocol.def | 15 ++++++ server/token.c | 119 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 258 insertions(+), 7 deletions(-)
diff --git a/dlls/ntdll/nt.c b/dlls/ntdll/nt.c index c3f5df337f..29fff96036 100644 --- a/dlls/ntdll/nt.c +++ b/dlls/ntdll/nt.c @@ -70,6 +70,135 @@ WINE_DEFAULT_DEBUG_CHANNEL(ntdll); * Token */
+static BOOL get_primary_group_index( const TOKEN_PRIMARY_GROUP *primary, const TOKEN_GROUPS *groups, + unsigned int *index ) +{ + unsigned int i; + for (i = 0; i < groups->GroupCount; i++) + { + if (RtlEqualSid( primary->PrimaryGroup, groups->Groups[i].Sid )) + { + *index = i + 1; /* 0 is reserved for user */ + return TRUE; + } + } + return FALSE; +} + +static BOOL get_owner_index( const TOKEN_OWNER *owner, const TOKEN_USER *user, const TOKEN_GROUPS *groups, + unsigned int *index ) +{ + unsigned int i; + if (RtlEqualSid( owner->Owner, user->User.Sid )) + { + *index = 0; + return TRUE; + } + for (i = 0; i < groups->GroupCount; i++) + { + if (RtlEqualSid( owner->Owner, groups->Groups[i].Sid )) + { + *index = i + 1; + return TRUE; + } + } + return FALSE; +} + +/* create flat groups buffer and store user as first entry */ +static struct token_groups *flatten_groups( const TOKEN_USER *user, const TOKEN_GROUPS *groups, data_size_t *size ) +{ + struct token_groups *ret; + unsigned int i, sid_len, *attr; + char *sid; + + *size = sizeof(*ret) + sizeof(*attr) + RtlLengthSid( user->User.Sid ); + for (i = 0; i < groups->GroupCount; i++) + { + *size += sizeof(*attr); + *size += RtlLengthSid( groups->Groups[i].Sid ); + } + + if (!(ret = RtlAllocateHeap( GetProcessHeap(), 0, *size ))) return NULL; + attr = (unsigned int *)(ret + 1); + sid = (char *)attr + (groups->GroupCount + 1) * sizeof(*attr); + + *attr++ = user->User.Attributes; + sid_len = RtlLengthSid( user->User.Sid ); + memcpy( sid, user->User.Sid, sid_len ); + sid += sid_len; + + for (i = 0; i < groups->GroupCount; i++) + { + *attr++ = groups->Groups[i].Attributes; + sid_len = RtlLengthSid( groups->Groups[i].Sid ); + memcpy( sid, groups->Groups[i].Sid, sid_len ); + sid += sid_len; + } + + ret->count = groups->GroupCount + 1; + return ret; +} + +static unsigned int acl_size( const ACL *acl ) +{ + unsigned int i, ret = sizeof(*acl); + ACE_HEADER *ace = (ACE_HEADER *)(acl + 1); + if (!acl) return 0; + for (i = 0; i < acl->AceCount; i++) ret += ace[i].AceSize; + return ret; +} + +NTSTATUS WINAPI NtCreateToken( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBUTES *attrs, TOKEN_TYPE type, + LUID *session, LARGE_INTEGER *expiration, TOKEN_USER *user, TOKEN_GROUPS *groups, + TOKEN_PRIVILEGES *privs, TOKEN_OWNER *owner, TOKEN_PRIMARY_GROUP *primary_group, + TOKEN_DEFAULT_DACL *dacl, TOKEN_SOURCE *source ) +{ + NTSTATUS status; + struct object_attributes *objattr; + struct token_groups *flat_groups; + data_size_t attr_size, flat_groups_size, default_dacl_size = dacl ? acl_size( dacl->DefaultDacl ) : 0; + ACL *default_dacl = dacl ? dacl->DefaultDacl : NULL; + unsigned int primary_group_index, owner_index; + + TRACE( "(%p,%08x,%s,%u,%p,%p,%p,%p,%p,%p,%p,%p,%p)\n", handle, access, debugstr_ObjectAttributes(attrs), + type, session, expiration, user, groups, privs, owner, primary_group, dacl, source ); + + if (!handle || !session || !expiration || !user || !groups || !privs || !owner || !primary_group || !source) + return STATUS_ACCESS_VIOLATION; + + if (!get_primary_group_index( primary_group, groups, &primary_group_index )) return STATUS_INVALID_PRIMARY_GROUP; + if (!get_owner_index( owner, user, groups, &owner_index )) return STATUS_INVALID_OWNER; + + if (!(flat_groups = flatten_groups( user, groups, &flat_groups_size ))) return STATUS_NO_MEMORY; + if ((status = alloc_object_attributes( attrs, &objattr, &attr_size ))) + { + RtlFreeHeap( GetProcessHeap(), 0, flat_groups ); + return status; + } + + SERVER_START_REQ( create_token ) + { + req->access = access; + req->primary = (type == TokenPrimary); + req->primary_group_index = primary_group_index; + req->owner_index = owner_index; + req->privilege_count = privs->PrivilegeCount; + req->default_dacl_size = default_dacl_size; + wine_server_add_data( req, objattr, attr_size ); + wine_server_add_data( req, privs->Privileges, privs->PrivilegeCount * sizeof(privs->Privileges[0]) ); + wine_server_add_data( req, default_dacl, default_dacl_size ); + wine_server_add_data( req, flat_groups, flat_groups_size ); + status = wine_server_call( req ); + if (!status) *handle = wine_server_ptr_handle( reply->token ); + } + SERVER_END_REQ; + + RtlFreeHeap( GetProcessHeap(), 0, flat_groups ); + RtlFreeHeap( GetProcessHeap(), 0, objattr ); + return status; +} + /****************************************************************************** * NtDuplicateToken [NTDLL.@] * ZwDuplicateToken [NTDLL.@] diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index c260b0dbec..27e51a165b 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -154,7 +154,7 @@ @ stub NtCreateThread @ stdcall NtCreateThreadEx(ptr long ptr long ptr ptr long long long long ptr) @ stdcall NtCreateTimer(ptr long ptr long) -@ stub NtCreateToken +@ stdcall NtCreateToken(ptr long ptr long ptr ptr ptr ptr ptr ptr ptr ptr ptr ) # @ stub NtCreateWaitablePort @ stdcall -arch=win32,arm64 NtCurrentTeb() # @ stub NtDebugActiveProcess diff --git a/server/protocol.def b/server/protocol.def index 35824ae952..3dae8ab521 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3286,6 +3286,21 @@ enum caret_state user_handle_t window; /* clipboard listener window */ @END
+/* Create a security token */ +@REQ(create_token) + unsigned int access; /* access rights to the new token */ + int primary; /* is the new token to be a primary one? */ + unsigned int primary_group_index; /* primary group index into groups array */ + unsigned int owner_index; /* owner index into groups array */ + unsigned int privilege_count; /* number of privileges */ + unsigned int default_dacl_size; /* size of default dacl */ + VARARG(objattr,object_attributes); /* object attributes */ + VARARG(default_dacl,ACL); /* token default dacl */ + VARARG(privileges,LUID_AND_ATTRIBUTES); /* token privileges */ + VARARG(groups,token_groups); /* token groups, first element is token user */ +@REPLY + obj_handle_t token; /* handle to the token */ +@END
/* Open a security token */ @REQ(open_token) diff --git a/server/token.c b/server/token.c index 0810a61353..172a3414ab 100644 --- a/server/token.c +++ b/server/token.c @@ -221,6 +221,11 @@ const SID *security_unix_uid_to_sid( uid_t uid ) return &anonymous_logon_sid; }
+static inline int sid_is_valid( const SID *sid, data_size_t size ) +{ + return (size >= FIELD_OFFSET( SID, SubAuthority[0] ) && size >= security_sid_len( sid )); +} + static int acl_is_valid( const ACL *acl, data_size_t size ) { ULONG i; @@ -269,8 +274,7 @@ static int acl_is_valid( const ACL *acl, data_size_t size ) default: return FALSE; } - if (sid_size < FIELD_OFFSET(SID, SubAuthority[0]) || sid_size < security_sid_len( sid )) - return FALSE; + if (!sid_is_valid( sid, sid_size )) return FALSE; ace = ace_next( ace ); } return TRUE; @@ -326,7 +330,6 @@ int sd_is_valid( const struct security_descriptor *sd, data_size_t size ) dacl = sd_get_dacl( sd, &dummy ); if (dacl && !acl_is_valid( dacl, sd->dacl_len )) return FALSE; - offset += sd->dacl_len;
return TRUE; } @@ -527,7 +530,7 @@ static void token_destroy( struct object *obj ) * modified_id may be NULL, indicating that a new modified_id luid should be * allocated. */ -static struct token *create_token( unsigned primary, const SID *user, +static struct token *token_create( unsigned primary, const SID *user, const SID_AND_ATTRIBUTES *groups, unsigned int group_count, const LUID_AND_ATTRIBUTES *privs, unsigned int priv_count, const ACL *default_dacl, TOKEN_SOURCE source, @@ -638,7 +641,7 @@ struct token *token_duplicate( struct token *src_token, unsigned primary, return NULL; }
- token = create_token( primary, src_token->user, NULL, 0, + token = token_create( primary, src_token->user, NULL, 0, NULL, 0, src_token->default_dacl, src_token->source, modified_id, impersonation_level ); @@ -836,7 +839,7 @@ struct token *token_create_admin( void ) { logon_sid, SE_GROUP_ENABLED|SE_GROUP_ENABLED_BY_DEFAULT|SE_GROUP_MANDATORY|SE_GROUP_LOGON_ID }, }; static const TOKEN_SOURCE admin_source = {"SeMgr", {0, 0}}; - token = create_token( TRUE, user_sid, admin_groups, sizeof(admin_groups)/sizeof(admin_groups[0]), + token = token_create( TRUE, user_sid, admin_groups, sizeof(admin_groups)/sizeof(admin_groups[0]), admin_privs, sizeof(admin_privs)/sizeof(admin_privs[0]), default_dacl, admin_source, NULL, -1 ); /* we really need a primary group */ @@ -1173,6 +1176,110 @@ int check_object_access(struct object *obj, unsigned int *access) return res; }
+/* create a security token */ +DECL_HANDLER(create_token) +{ + static const TOKEN_SOURCE source = {"SeMgr", {0, 0}}; + struct token *token; + struct unicode_str name; + const struct security_descriptor *sd; + const struct object_attributes *objattr = get_req_object_attributes( &sd, &name, NULL ); + data_size_t data_size, privs_size; + const LUID_AND_ATTRIBUTES *privs; + const ACL *acl = NULL; + ACL *default_dacl = NULL; + const struct token_groups *groups; + const unsigned int *attr; + unsigned int i; + const SID *sid; + + if (!objattr) return; + + privs = get_req_data_after_objattr( objattr, &data_size ); + privs_size = req->privilege_count * sizeof(*privs); + if (privs_size > data_size) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + data_size -= privs_size; + + if (req->default_dacl_size) + { + acl = (const ACL *)((const char *)privs + privs_size); + if (!acl_is_valid( acl, req->default_dacl_size )) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + } + data_size -= req->default_dacl_size; + + groups = (const struct token_groups *)((const char *)privs + privs_size + req->default_dacl_size); + if (data_size < sizeof(*groups) || !groups->count || + sizeof(*groups) + groups->count * sizeof(*attr) > data_size || + req->primary_group_index < 1 || req->primary_group_index >= groups->count || + req->owner_index >= groups->count) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + attr = (const unsigned int *)(groups + 1); + data_size -= sizeof(*groups) + groups->count * sizeof(*attr); + + sid = (const SID *)(attr + groups->count); + for (i = 0; i < groups->count; i++) + { + if (!sid_is_valid( sid, data_size )) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + data_size -= security_sid_len( sid ); + sid = (const SID *)((const char *)sid + security_sid_len( sid )); + } + + if (!acl) acl = default_dacl = create_default_dacl( sid ); + sid = (const SID *)(attr + groups->count); /* first entry is user */ + + if ((token = token_create( req->primary, sid, NULL, 0, privs, req->privilege_count, acl, source, NULL, + SecurityIdentification ))) + { + sid = (const SID *)((const char *)sid + security_sid_len( sid )); /* skip user */ + attr++; + for (i = 1; i < groups->count; i++) + { + struct group *group; + if (!(group = mem_alloc( FIELD_OFFSET(struct group, sid.SubAuthority[sid->SubAuthorityCount]) ))) + { + release_object( token ); + return; + } + + memcpy( &group->sid, sid, security_sid_len( sid ) ); + group->enabled = (*attr & SE_GROUP_ENABLED) != 0; + group->def = (*attr & SE_GROUP_ENABLED_BY_DEFAULT) != 0; + group->logon = (*attr & SE_GROUP_LOGON_ID) != 0; + group->mandatory = (*attr & SE_GROUP_MANDATORY) != 0; + group->owner = (*attr & SE_GROUP_OWNER) != 0; + group->resource = (*attr & SE_GROUP_RESOURCE) != 0; + group->deny_only = (*attr & SE_GROUP_USE_FOR_DENY_ONLY) != 0; + list_add_tail( &token->groups, &group->entry ); + + if (req->primary_group_index == i) token->primary_group = &group->sid; + if (req->owner_index == i) token->owner = &group->sid; + + sid = (const SID *)((const char *)sid + security_sid_len( sid )); + attr++; + } + if (!req->owner_index) token->owner = token->user; + + reply->token = alloc_handle( current->process, token, req->access, objattr->attributes ); + release_object( token ); + } + + free( default_dacl ); +}
/* open a security token */ DECL_HANDLER(open_token)
Hans Leidekker hans@codeweavers.com writes:
You are not being paranoid enough...
- privs = get_req_data_after_objattr( objattr, &data_size );
- privs_size = req->privilege_count * sizeof(*privs);
This can overflow.
- if (req->default_dacl_size)
- {
acl = (const ACL *)((const char *)privs + privs_size);
if (!acl_is_valid( acl, req->default_dacl_size ))
No check against request size.
- data_size -= req->default_dacl_size;
- groups = (const struct token_groups *)((const char *)privs + privs_size + req->default_dacl_size);
size could be misaligned.
- if (data_size < sizeof(*groups) || !groups->count ||
sizeof(*groups) + groups->count * sizeof(*attr) > data_size ||
This can overflow.
There may be more... Also you probably want to introduce some kind of helper function.