From e77f05ee52e8481f69edcdfaf111bd8e8daf1d00 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Fri, 7 Feb 2014 20:20:32 -0700 Subject: [PATCH] Add support for inherited file ACLs. --- README.md | 1 + ...-storage-of-security-attributes-for-.patch | 135 ++++++++ ...curity-attributes-from-parent-direct.patch | 178 ---------- ...-retrieval-of-security-attributes-fo.patch | 133 ++++++++ ...return-security-attributes-with-ext.patch} | 228 ++++--------- ...ecurity-attributes-from-parent-direc.patch | 300 +++++++++++++++++ ...ecurity-attributes-from-parent-direc.patch | 309 ++++++++++++++++++ ...default-security-attributes-for-user.patch | 139 ++++++++ .../92938b89-506b-430a-ba50-32de8b286e56.def | 2 +- patches/patch-list.patch | 2 +- 10 files changed, 1090 insertions(+), 337 deletions(-) create mode 100644 patches/02-ACL_Extended_Attributes/0001-server-Unify-the-storage-of-security-attributes-for-.patch delete mode 100644 patches/02-ACL_Extended_Attributes/0002-ntdll-Inherit-security-attributes-from-parent-direct.patch create mode 100644 patches/02-ACL_Extended_Attributes/0002-server-Unify-the-retrieval-of-security-attributes-fo.patch rename patches/02-ACL_Extended_Attributes/{0001-server-Store-and-return-security-attributes-with-ext.patch => 0003-server-Store-and-return-security-attributes-with-ext.patch} (69%) create mode 100644 patches/02-ACL_Extended_Attributes/0004-server-Inherit-security-attributes-from-parent-direc.patch create mode 100644 patches/02-ACL_Extended_Attributes/0005-server-Inherit-security-attributes-from-parent-direc.patch create mode 100644 patches/02-ACL_Extended_Attributes/0006-shell32-Set-the-default-security-attributes-for-user.patch diff --git a/README.md b/README.md index 92853be2..77308d39 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Daily updates for the Wine "Compholio" Edition. Current patches include: * Support for interface change notifications (http://bugs.winehq.org/show_bug.cgi?id=32328) * Support for stored file ACLs (http://bugs.winehq.org/show_bug.cgi?id=31858) +* Support for inherited file ACLs (http://bugs.winehq.org/show_bug.cgi?id=34406) * Support for Junction Points (http://bugs.winehq.org/show_bug.cgi?id=12401) * Support for TransmitFile (http://bugs.winehq.org/show_bug.cgi?id=5048) * Support for access security with named pipes (http://bugs.winehq.org/show_bug.cgi?id=34098) diff --git a/patches/02-ACL_Extended_Attributes/0001-server-Unify-the-storage-of-security-attributes-for-.patch b/patches/02-ACL_Extended_Attributes/0001-server-Unify-the-storage-of-security-attributes-for-.patch new file mode 100644 index 00000000..1ce8ca02 --- /dev/null +++ b/patches/02-ACL_Extended_Attributes/0001-server-Unify-the-storage-of-security-attributes-for-.patch @@ -0,0 +1,135 @@ +From 8714b20a24dcfed7853a9483fd8ef04dd3292fd0 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 6 Feb 2014 18:21:11 -0700 +Subject: server: Unify the storage of security attributes for files and + directories. + +--- + server/change.c | 46 ++++++---------------------------------------- + server/file.c | 25 +++++++++++++++++-------- + server/file.h | 2 ++ + 3 files changed, 25 insertions(+), 48 deletions(-) + +diff --git a/server/change.c b/server/change.c +index f6d56b0..76fc9f7 100644 +--- a/server/change.c ++++ b/server/change.c +@@ -317,49 +317,15 @@ static struct security_descriptor *dir_get_sd( struct object *obj ) + static int dir_set_sd( struct object *obj, const struct security_descriptor *sd, + unsigned int set_info ) + { +- struct dir *dir = (struct dir *)obj; +- const SID *owner; +- struct stat st; +- mode_t mode; +- int unix_fd; ++ struct fd *fd; ++ int ret; + + assert( obj->ops == &dir_ops ); + +- unix_fd = get_dir_unix_fd( dir ); +- +- if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) return 1; +- +- if (set_info & OWNER_SECURITY_INFORMATION) +- { +- owner = sd_get_owner( sd ); +- if (!owner) +- { +- set_error( STATUS_INVALID_SECURITY_DESCR ); +- return 0; +- } +- if (!obj->sd || !security_equal_sid( owner, sd_get_owner( obj->sd ) )) +- { +- /* FIXME: get Unix uid and call fchown */ +- } +- } +- else if (obj->sd) +- owner = sd_get_owner( obj->sd ); +- else +- owner = token_get_user( current->process->token ); +- +- if (set_info & DACL_SECURITY_INFORMATION) +- { +- /* keep the bits that we don't map to access rights in the ACL */ +- mode = st.st_mode & (S_ISUID|S_ISGID|S_ISVTX); +- mode |= sd_to_mode( sd, owner ); +- +- if (((st.st_mode ^ mode) & (S_IRWXU|S_IRWXG|S_IRWXO)) && fchmod( unix_fd, mode ) == -1) +- { +- file_set_error(); +- return 0; +- } +- } +- return 1; ++ fd = dir_get_fd( obj ); ++ ret = file_set_acls( obj, fd, sd, set_info ); ++ release_object( fd ); ++ return ret; + } + + static struct change_record *get_first_change_record( struct dir *dir ) +diff --git a/server/file.c b/server/file.c +index cceb8ad..13ebaf9 100644 +--- a/server/file.c ++++ b/server/file.c +@@ -534,18 +534,13 @@ mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner ) + return new_mode & ~denied_mode; + } + +-static int file_set_sd( struct object *obj, const struct security_descriptor *sd, +- unsigned int set_info ) ++int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *sd, ++ unsigned int set_info ) + { +- struct file *file = (struct file *)obj; ++ int unix_fd = get_unix_fd( fd ); + const SID *owner; + struct stat st; + mode_t mode; +- int unix_fd; +- +- assert( obj->ops == &file_ops ); +- +- unix_fd = get_file_unix_fd( file ); + + if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) return 1; + +@@ -584,6 +579,20 @@ static int file_set_sd( struct object *obj, const struct security_descriptor *sd + return 1; + } + ++static int file_set_sd( struct object *obj, const struct security_descriptor *sd, ++ unsigned int set_info ) ++{ ++ struct fd *fd; ++ int ret; ++ ++ assert( obj->ops == &file_ops ); ++ ++ fd = file_get_fd( obj ); ++ ret = file_set_acls( obj, fd, sd, set_info ); ++ release_object( fd ); ++ return ret; ++} ++ + static void file_destroy( struct object *obj ) + { + struct file *file = (struct file *)obj; +diff --git a/server/file.h b/server/file.h +index 493d30b..11c3220 100644 +--- a/server/file.h ++++ b/server/file.h +@@ -122,6 +122,8 @@ extern struct file *create_file_for_fd_obj( struct fd *fd, unsigned int access, + extern void file_set_error(void); + extern struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, const SID *group ); + extern mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner ); ++extern int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *sd, ++ unsigned int set_info ); + + /* file mapping functions */ + +-- +1.7.9.5 + diff --git a/patches/02-ACL_Extended_Attributes/0002-ntdll-Inherit-security-attributes-from-parent-direct.patch b/patches/02-ACL_Extended_Attributes/0002-ntdll-Inherit-security-attributes-from-parent-direct.patch deleted file mode 100644 index 4d84fce2..00000000 --- a/patches/02-ACL_Extended_Attributes/0002-ntdll-Inherit-security-attributes-from-parent-direct.patch +++ /dev/null @@ -1,178 +0,0 @@ -From b5efc991444e6539adf73f42b44e333b8aa7de2f Mon Sep 17 00:00:00 2001 -From: "Erich E. Hoover" -Date: Mon, 13 Jan 2014 18:32:05 -0700 -Subject: ntdll: Inherit security attributes from parent directories. - ---- - dlls/advapi32/tests/security.c | 40 ++++++++++++++++++- - dlls/ntdll/file.c | 83 +++++++++++++++++++++++++++++++++++++++- - 2 files changed, 121 insertions(+), 2 deletions(-) - -diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c -index 297cf96..4b1c5a2 100644 ---- a/dlls/advapi32/tests/security.c -+++ b/dlls/advapi32/tests/security.c -@@ -3030,10 +3030,11 @@ static void test_CreateDirectoryA(void) - ACL_SIZE_INFORMATION acl_size; - ACCESS_ALLOWED_ACE *ace; - SECURITY_ATTRIBUTES sa; -+ char tmpfile[MAX_PATH]; - char tmpdir[MAX_PATH]; -+ HANDLE token, hTemp; - struct _SID *owner; - BOOL bret = TRUE; -- HANDLE token; - DWORD error; - PACL pDacl; - -@@ -3125,6 +3126,43 @@ static void test_CreateDirectoryA(void) - ace->Mask); - } - -+ /* Test inheritance of ACLs */ -+ strcpy(tmpfile, tmpdir); -+ lstrcatA(tmpfile, "/tmpfile"); -+ hTemp = CreateFileA(tmpfile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, -+ FILE_FLAG_DELETE_ON_CLOSE, NULL); -+ error = pGetNamedSecurityInfoA(tmpfile, SE_FILE_OBJECT, -+ OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, (PSID*)&owner, -+ NULL, &pDacl, NULL, &pSD); -+ ok(error == ERROR_SUCCESS, "Failed to get permissions on file.\n"); -+ bret = pGetAclInformation(pDacl, &acl_size, sizeof(acl_size), AclSizeInformation); -+ ok(bret, "GetAclInformation failed\n"); -+ ok(acl_size.AceCount == 2, "GetAclInformation returned unexpected entry count (%d != 2).\n", -+ acl_size.AceCount); -+ if (acl_size.AceCount > 0) -+ { -+ bret = pGetAce(pDacl, 0, (VOID **)&ace); -+ ok(bret, "Inherited Failed to get Current User ACE.\n"); -+ bret = EqualSid(&ace->SidStart, user_sid); -+ ok(bret, "Inherited Current User ACE != Current User SID.\n"); -+ ok(((ACE_HEADER *)ace)->AceFlags == INHERITED_ACE, -+ "Inherited Current User ACE has unexpected flags (0x%x != 0x10)\n", ((ACE_HEADER *)ace)->AceFlags); -+ ok(ace->Mask == 0x1f01ff, "Current User ACE has unexpected mask (0x%x != 0x1f01ff)\n", -+ ace->Mask); -+ } -+ if (acl_size.AceCount > 1) -+ { -+ bret = pGetAce(pDacl, 1, (VOID **)&ace); -+ ok(bret, "Inherited Failed to get Administators Group ACE.\n"); -+ bret = EqualSid(&ace->SidStart, admin_sid); -+ ok(bret, "Inherited Administators Group ACE != Administators Group SID.\n"); -+ ok(((ACE_HEADER *)ace)->AceFlags == INHERITED_ACE, -+ "Inherited Administators Group ACE has unexpected flags (0x%x != 0x10)\n", ((ACE_HEADER *)ace)->AceFlags); -+ ok(ace->Mask == 0x1f01ff, "Administators Group ACE has unexpected mask (0x%x != 0x1f01ff)\n", -+ ace->Mask); -+ } -+ CloseHandle(hTemp); -+ - done: - HeapFree(GetProcessHeap(), 0, user); - bret = RemoveDirectoryA(tmpdir); -diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c -index d2efcc1..bdcaab4 100644 ---- a/dlls/ntdll/file.c -+++ b/dlls/ntdll/file.c -@@ -103,6 +103,79 @@ mode_t FILE_umask = 0; - - static const WCHAR ntfsW[] = {'N','T','F','S'}; - -+static NTSTATUS FILE_CreateFile( PHANDLE handle, ACCESS_MASK access, POBJECT_ATTRIBUTES attr, -+ PIO_STATUS_BLOCK io, PLARGE_INTEGER alloc_size, -+ ULONG attributes, ULONG sharing, ULONG disposition, -+ ULONG options, PVOID ea_buffer, ULONG ea_length ); -+ -+struct security_descriptor *FILE_get_parent_sd(UNICODE_STRING *filenameW) -+{ -+ SECURITY_INFORMATION info = OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION -+ |DACL_SECURITY_INFORMATION|SACL_SECURITY_INFORMATION; -+ WCHAR *p, parent[UNICODE_STRING_MAX_CHARS]; -+ PSECURITY_DESCRIPTOR parentsd = NULL; -+ ACL_SIZE_INFORMATION acl_size; -+ BOOLEAN present, defaulted; -+ OBJECT_ATTRIBUTES pattr; -+ UNICODE_STRING parentW; -+ IO_STATUS_BLOCK io; -+ NTSTATUS status; -+ HANDLE hparent; -+ ULONG n1, n2; -+ PACL pDacl; -+ int i; -+ -+ parentW.Buffer = parent; -+ parentW.Length = filenameW->Length; -+ memcpy(parentW.Buffer, filenameW->Buffer, filenameW->Length); -+ if ((p = strrchrW(parent, '\\')) == NULL) return NULL; -+ p[0] = 0; -+ parentW.Length = (p-parent)*sizeof(WCHAR); -+ memset(&pattr, 0x0, sizeof(pattr)); -+ pattr.Length = sizeof(pattr); -+ pattr.Attributes = OBJ_CASE_INSENSITIVE; -+ pattr.ObjectName = &parentW; -+ status = FILE_CreateFile( &hparent, READ_CONTROL|ACCESS_SYSTEM_SECURITY, &pattr, &io, NULL, -+ FILE_FLAG_BACKUP_SEMANTICS, -+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, FILE_OPEN, -+ FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0 ); -+ if (status == STATUS_SUCCESS) -+ status = NtQuerySecurityObject( hparent, info, NULL, 0, &n1 ); -+ if (status == STATUS_BUFFER_TOO_SMALL && (parentsd = RtlAllocateHeap( GetProcessHeap(), 0, n1 )) != NULL) -+ status = NtQuerySecurityObject( hparent, info, parentsd, n1, &n2 ); -+ if (status == STATUS_SUCCESS) -+ status = NtQuerySecurityObject( hparent, info, parentsd, n1, &n2 ); -+ if (hparent != INVALID_HANDLE_VALUE) -+ NtClose( hparent ); -+ if (status != STATUS_SUCCESS) return NULL; -+ status = RtlGetDaclSecurityDescriptor(parentsd, &present, &pDacl, &defaulted); -+ if (status != STATUS_SUCCESS || !present) return NULL; -+ status = RtlQueryInformationAcl(pDacl, &acl_size, sizeof(acl_size), AclSizeInformation); -+ if (status != STATUS_SUCCESS) return NULL; -+ -+ for (i=acl_size.AceCount-1; i>=0; i--) -+ { -+ DWORD inheritance_mask = INHERIT_ONLY_ACE|OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE; -+ ACE_HEADER *ace; -+ -+ status = RtlGetAce(pDacl, i, (VOID **)&ace); -+ if (status != STATUS_SUCCESS || !(ace->AceFlags & inheritance_mask)) -+ { -+ RtlDeleteAce(pDacl, i); -+ acl_size.AceCount--; -+ } -+ else -+ ace->AceFlags = (ace->AceFlags & ~inheritance_mask) | INHERITED_ACE; -+ } -+ -+ if (!acl_size.AceCount) -+ { -+ return NULL; -+ } -+ return parentsd; -+} -+ -+ - /************************************************************************** - * FILE_CreateFile (internal) - * Open a file. -@@ -161,10 +234,18 @@ static NTSTATUS FILE_CreateFile( PHANDLE handle, ACCESS_MASK access, POBJECT_ATT - { - struct security_descriptor *sd; - struct object_attributes objattr; -+ PSECURITY_DESCRIPTOR parentsd = NULL, psd; - - objattr.rootdir = wine_server_obj_handle( attr->RootDirectory ); - objattr.name_len = 0; -- io->u.Status = NTDLL_create_struct_sd( attr->SecurityDescriptor, &sd, &objattr.sd_len ); -+ psd = attr->SecurityDescriptor; -+ if (!psd && (disposition == FILE_CREATE||disposition == FILE_OVERWRITE_IF)) -+ parentsd = FILE_get_parent_sd( attr->ObjectName ); -+ if (parentsd) -+ psd = parentsd; -+ io->u.Status = NTDLL_create_struct_sd( psd, &sd, &objattr.sd_len ); -+ if (parentsd) -+ RtlFreeHeap( GetProcessHeap(), 0, parentsd ); - if (io->u.Status != STATUS_SUCCESS) - { - RtlFreeAnsiString( &unix_name ); --- -1.7.9.5 - diff --git a/patches/02-ACL_Extended_Attributes/0002-server-Unify-the-retrieval-of-security-attributes-fo.patch b/patches/02-ACL_Extended_Attributes/0002-server-Unify-the-retrieval-of-security-attributes-fo.patch new file mode 100644 index 00000000..0e59bd15 --- /dev/null +++ b/patches/02-ACL_Extended_Attributes/0002-server-Unify-the-retrieval-of-security-attributes-fo.patch @@ -0,0 +1,133 @@ +From ca4cf8fe5ed71c5ddcd5a41ae35f95e9d0f0767e Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 6 Feb 2014 18:32:21 -0700 +Subject: server: Unify the retrieval of security attributes for files and + directories. + +--- + server/change.c | 27 +++++---------------------- + server/file.c | 34 ++++++++++++++++++++++------------ + server/file.h | 2 ++ + 3 files changed, 29 insertions(+), 34 deletions(-) + +diff --git a/server/change.c b/server/change.c +index 76fc9f7..0b7b979 100644 +--- a/server/change.c ++++ b/server/change.c +@@ -286,31 +286,14 @@ static int get_dir_unix_fd( struct dir *dir ) + static struct security_descriptor *dir_get_sd( struct object *obj ) + { + struct dir *dir = (struct dir *)obj; +- int unix_fd; +- struct stat st; + struct security_descriptor *sd; +- assert( obj->ops == &dir_ops ); +- +- unix_fd = get_dir_unix_fd( dir ); +- +- if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) +- return obj->sd; +- +- /* mode and uid the same? if so, no need to re-generate security descriptor */ +- if (obj->sd && +- (st.st_mode & (S_IRWXU|S_IRWXO)) == (dir->mode & (S_IRWXU|S_IRWXO)) && +- (st.st_uid == dir->uid)) +- return obj->sd; ++ struct fd *fd; + +- sd = mode_to_sd( st.st_mode, +- security_unix_uid_to_sid( st.st_uid ), +- token_get_primary_group( current->process->token )); +- if (!sd) return obj->sd; ++ assert( obj->ops == &dir_ops ); + +- dir->mode = st.st_mode; +- dir->uid = st.st_uid; +- free( obj->sd ); +- obj->sd = sd; ++ fd = dir_get_fd( obj ); ++ sd = file_get_acls( obj, fd, &dir->mode, &dir->uid ); ++ release_object( fd ); + return sd; + } + +diff --git a/server/file.c b/server/file.c +index 13ebaf9..8baa712 100644 +--- a/server/file.c ++++ b/server/file.c +@@ -424,23 +424,19 @@ struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, const SID + return sd; + } + +-static struct security_descriptor *file_get_sd( struct object *obj ) ++struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mode_t *mode, ++ uid_t *uid ) + { +- struct file *file = (struct file *)obj; +- struct stat st; +- int unix_fd; ++ int unix_fd = get_unix_fd( fd ); + struct security_descriptor *sd; +- +- assert( obj->ops == &file_ops ); +- +- unix_fd = get_file_unix_fd( file ); ++ struct stat st; + + if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) + return obj->sd; + + /* mode and uid the same? if so, no need to re-generate security descriptor */ +- if (obj->sd && (st.st_mode & (S_IRWXU|S_IRWXO)) == (file->mode & (S_IRWXU|S_IRWXO)) && +- (st.st_uid == file->uid)) ++ if (obj->sd && (st.st_mode & (S_IRWXU|S_IRWXO)) == (*mode & (S_IRWXU|S_IRWXO)) && ++ (st.st_uid == *uid)) + return obj->sd; + + sd = mode_to_sd( st.st_mode, +@@ -448,13 +444,27 @@ static struct security_descriptor *file_get_sd( struct object *obj ) + token_get_primary_group( current->process->token )); + if (!sd) return obj->sd; + +- file->mode = st.st_mode; +- file->uid = st.st_uid; ++ *mode = st.st_mode; ++ *uid = st.st_uid; + free( obj->sd ); + obj->sd = sd; + return sd; + } + ++static struct security_descriptor *file_get_sd( struct object *obj ) ++{ ++ struct file *file = (struct file *)obj; ++ struct security_descriptor *sd; ++ struct fd *fd; ++ ++ assert( obj->ops == &file_ops ); ++ ++ fd = file_get_fd( obj ); ++ sd = file_get_acls( obj, fd, &file->mode, &file->uid ); ++ release_object( fd ); ++ return sd; ++} ++ + static mode_t file_access_to_mode( unsigned int access ) + { + mode_t mode = 0; +diff --git a/server/file.h b/server/file.h +index 11c3220..89b5888 100644 +--- a/server/file.h ++++ b/server/file.h +@@ -124,6 +124,8 @@ extern struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, con + extern mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner ); + extern int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *sd, + unsigned int set_info ); ++extern struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mode_t *mode, ++ uid_t *uid ); + + /* file mapping functions */ + +-- +1.7.9.5 + diff --git a/patches/02-ACL_Extended_Attributes/0001-server-Store-and-return-security-attributes-with-ext.patch b/patches/02-ACL_Extended_Attributes/0003-server-Store-and-return-security-attributes-with-ext.patch similarity index 69% rename from patches/02-ACL_Extended_Attributes/0001-server-Store-and-return-security-attributes-with-ext.patch rename to patches/02-ACL_Extended_Attributes/0003-server-Store-and-return-security-attributes-with-ext.patch index fe749c1a..93703bfb 100644 --- a/patches/02-ACL_Extended_Attributes/0001-server-Store-and-return-security-attributes-with-ext.patch +++ b/patches/02-ACL_Extended_Attributes/0003-server-Store-and-return-security-attributes-with-ext.patch @@ -1,20 +1,17 @@ -From acc6aac3356b0ec9024362488016945b76f67111 Mon Sep 17 00:00:00 2001 +From 9eeab4088f54ab8f00fa36e3d3046e756a732a1e Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" -Date: Sat, 11 Jan 2014 09:18:16 -0700 +Date: Thu, 6 Feb 2014 23:13:11 -0700 Subject: server: Store and return security attributes with extended file attributes. --- - configure.ac | 6 ++ - dlls/advapi32/tests/security.c | 25 +++--- - server/change.c | 11 ++- - server/fd.c | 68 +++++++++++++++- - server/file.c | 176 +++++++++++++++++++++++++++++++++++++++- - server/file.h | 5 +- - 6 files changed, 269 insertions(+), 22 deletions(-) + configure.ac | 6 + + dlls/advapi32/tests/security.c | 25 ++-- + server/file.c | 245 +++++++++++++++++++++++++++++++++++++++- + 3 files changed, 260 insertions(+), 16 deletions(-) diff --git a/configure.ac b/configure.ac -index dcabb55..fc8bd5c 100644 +index de807d2..ddf0000 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,7 @@ AC_ARG_WITH(pthread, AS_HELP_STRING([--without-pthread],[do not use the pthrea @@ -25,7 +22,7 @@ index dcabb55..fc8bd5c 100644 AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_Xcomposite_h=no; fi]) AC_ARG_WITH(xcursor, AS_HELP_STRING([--without-xcursor],[do not use the Xcursor extension]), -@@ -665,6 +666,11 @@ AC_CHECK_HEADERS([libprocstat.h],,, +@@ -660,6 +661,11 @@ AC_CHECK_HEADERS([libprocstat.h],,, #include #endif]) @@ -38,7 +35,7 @@ index dcabb55..fc8bd5c 100644 AC_SUBST(dlldir,"\${libdir}/wine") diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c -index 2efe80e..297cf96 100644 +index f3cc85d..ceea60e 100644 --- a/dlls/advapi32/tests/security.c +++ b/dlls/advapi32/tests/security.c @@ -3105,10 +3105,10 @@ static void test_CreateDirectoryA(void) @@ -108,49 +105,21 @@ index 2efe80e..297cf96 100644 ok(((ACE_HEADER *)ace)->AceFlags == 0, "Administators Group ACE has unexpected flags (0x%x != 0x0)\n", ((ACE_HEADER *)ace)->AceFlags); ok(ace->Mask == 0x1f01ff, "Administators Group ACE has unexpected mask (0x%x != 0x1f01ff)\n", -diff --git a/server/change.c b/server/change.c -index f6d56b0..022c780 100644 ---- a/server/change.c -+++ b/server/change.c -@@ -286,6 +286,7 @@ static int get_dir_unix_fd( struct dir *dir ) - static struct security_descriptor *dir_get_sd( struct object *obj ) - { - struct dir *dir = (struct dir *)obj; -+ const SID *user, *group; - int unix_fd; - struct stat st; - struct security_descriptor *sd; -@@ -302,9 +303,11 @@ static struct security_descriptor *dir_get_sd( struct object *obj ) - (st.st_uid == dir->uid)) - return obj->sd; - -- sd = mode_to_sd( st.st_mode, -- security_unix_uid_to_sid( st.st_uid ), -- token_get_primary_group( current->process->token )); -+ user = security_unix_uid_to_sid( st.st_uid ); -+ group = token_get_primary_group( current->process->token ); -+ sd = get_file_acls( unix_fd, user, group ); -+ if (!sd) -+ sd = mode_to_sd( st.st_mode, user, group ); - if (!sd) return obj->sd; - - dir->mode = st.st_mode; -@@ -353,6 +356,8 @@ static int dir_set_sd( struct object *obj, const struct security_descriptor *sd, - mode = st.st_mode & (S_ISUID|S_ISGID|S_ISVTX); - mode |= sd_to_mode( sd, owner ); - -+ set_file_acls( unix_fd, sd ); -+ - if (((st.st_mode ^ mode) & (S_IRWXU|S_IRWXG|S_IRWXO)) && fchmod( unix_fd, mode ) == -1) - { - file_set_error(); -diff --git a/server/fd.c b/server/fd.c -index fa8874c..98e3eca 100644 ---- a/server/fd.c -+++ b/server/fd.c -@@ -91,6 +91,9 @@ - #ifdef HAVE_SYS_SYSCALL_H - #include +diff --git a/server/file.c b/server/file.c +index 8baa712..0df2245 100644 +--- a/server/file.c ++++ b/server/file.c +@@ -32,6 +32,7 @@ + #include + #include + #include ++#include + #include + #ifdef HAVE_UTIME_H + #include +@@ -39,6 +40,9 @@ + #ifdef HAVE_POLL_H + #include #endif +#ifdef HAVE_ATTR_XATTR_H +#include @@ -158,19 +127,11 @@ index fa8874c..98e3eca 100644 #include "ntstatus.h" #define WIN32_NO_STATUS -@@ -99,6 +102,7 @@ - #include "handle.h" - #include "process.h" - #include "request.h" -+#include "security.h" - - #include "winternl.h" - #include "winioctl.h" -@@ -1726,9 +1730,69 @@ static char *dup_fd_name( struct fd *root, const char *name ) - return ret; +@@ -178,6 +182,66 @@ static struct object *create_file_obj( struct fd *fd, unsigned int access, mode_ + return &file->obj; } -+void set_file_acls( int fd, const struct security_descriptor *sd ) ++void set_xattr_acls( int fd, const struct security_descriptor *sd ) +{ +#ifdef HAVE_ATTR_XATTR_H + char buffer[XATTR_SIZE_MAX], *p = buffer; @@ -230,58 +191,22 @@ index fa8874c..98e3eca 100644 +#endif +} + - /* open() wrapper that returns a struct fd with no fd user set */ - struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, unsigned int access, -- unsigned int sharing, unsigned int options ) -+ unsigned int sharing, unsigned int options, const struct security_descriptor *sd ) - { - struct stat st; - struct closed_fd *closed_fd; -@@ -1804,6 +1868,8 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, - } - } - -+ set_file_acls( fd->unix_fd, sd ); -+ - closed_fd->unix_fd = fd->unix_fd; - closed_fd->unlink[0] = 0; - fstat( fd->unix_fd, &st ); -diff --git a/server/file.c b/server/file.c -index cceb8ad..9ac9188 100644 ---- a/server/file.c -+++ b/server/file.c -@@ -32,6 +32,7 @@ - #include - #include - #include -+#include - #include - #ifdef HAVE_UTIME_H - #include -@@ -39,6 +40,9 @@ - #ifdef HAVE_POLL_H - #include - #endif -+#ifdef HAVE_ATTR_XATTR_H -+#include -+#endif - - #include "ntstatus.h" - #define WIN32_NO_STATUS -@@ -237,7 +241,7 @@ static struct object *create_file( struct fd *root, const char *nameptr, data_si - access = generic_file_map_access( access ); - + static struct object *create_file( struct fd *root, const char *nameptr, data_size_t len, + unsigned int access, unsigned int sharing, int create, + unsigned int options, unsigned int attrs, +@@ -239,6 +303,7 @@ static struct object *create_file( struct fd *root, const char *nameptr, data_si /* FIXME: should set error to STATUS_OBJECT_NAME_COLLISION if file existed before */ -- fd = open_fd( root, name, flags | O_NONBLOCK | O_LARGEFILE, &mode, access, sharing, options ); -+ fd = open_fd( root, name, flags | O_NONBLOCK | O_LARGEFILE, &mode, access, sharing, options, sd ); + fd = open_fd( root, name, flags | O_NONBLOCK | O_LARGEFILE, &mode, access, sharing, options ); if (!fd) goto done; ++ set_xattr_acls( get_unix_fd( fd ), sd ); if (S_ISDIR(mode)) -@@ -424,9 +428,169 @@ struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, const SID + obj = create_dir_obj( fd, access, mode ); +@@ -424,11 +489,181 @@ struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, const SID return sd; } -+struct security_descriptor *get_file_acls( int fd, const SID *user, const SID *group ) ++struct security_descriptor *get_xattr_acls( int fd, const SID *user, const SID *group ) +{ +#ifdef HAVE_ATTR_XATTR_H + int ace_count = 0, dacl_size = sizeof(ACL), i, n; @@ -397,17 +322,6 @@ index cceb8ad..9ac9188 100644 + while(p); + sid->SubAuthorityCount = sub_authority_count; + -+ /* Convert generic rights into standard access rights */ -+ if (mask & GENERIC_ALL) -+ mask |= WRITE_DAC | WRITE_OWNER | DELETE | FILE_DELETE_CHILD; -+ if (mask & (GENERIC_ALL|GENERIC_READ)) -+ mask |= FILE_GENERIC_READ; -+ if (mask & (GENERIC_ALL|GENERIC_WRITE)) -+ mask |= FILE_GENERIC_WRITE; -+ if (mask & (GENERIC_ALL|GENERIC_EXECUTE)) -+ mask |= FILE_GENERIC_EXECUTE; -+ mask &= 0x0FFFFFFF; -+ + /* Handle the specific ACE */ + switch (type) + { @@ -440,15 +354,38 @@ index cceb8ad..9ac9188 100644 +#endif +} + - static struct security_descriptor *file_get_sd( struct object *obj ) ++/* Convert generic rights into standard access rights */ ++void convert_generic_sd( struct security_descriptor *sd ) ++{ ++ const ACL *dacl; ++ int present; ++ ++ dacl = sd_get_dacl( sd, &present ); ++ if (present && dacl) ++ { ++ const ACE_HEADER *ace = (const ACE_HEADER *)(dacl + 1); ++ ULONG i; ++ ++ for (i = 0; i < dacl->AceCount; i++, ace = ace_next( ace )) ++ { ++ DWORD *mask = (DWORD *)(ace + 1); ++ ++ *mask = generic_file_map_access( *mask ); ++ } ++ } ++} ++ + struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mode_t *mode, + uid_t *uid ) { - struct file *file = (struct file *)obj; + int unix_fd = get_unix_fd( fd ); + struct security_descriptor *sd; + const SID *user, *group; struct stat st; - int unix_fd; - struct security_descriptor *sd; -@@ -443,9 +607,11 @@ static struct security_descriptor *file_get_sd( struct object *obj ) - (st.st_uid == file->uid)) + + if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) +@@ -439,9 +674,11 @@ struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mo + (st.st_uid == *uid)) return obj->sd; - sd = mode_to_sd( st.st_mode, @@ -456,44 +393,21 @@ index cceb8ad..9ac9188 100644 - token_get_primary_group( current->process->token )); + user = security_unix_uid_to_sid( st.st_uid ); + group = token_get_primary_group( current->process->token ); -+ sd = get_file_acls( unix_fd, user, group ); -+ if (!sd) -+ sd = mode_to_sd( st.st_mode, user, group); ++ sd = get_xattr_acls( unix_fd, user, group ); ++ if (sd) convert_generic_sd( sd ); ++ if (!sd) sd = mode_to_sd( st.st_mode, user, group); if (!sd) return obj->sd; - file->mode = st.st_mode; -@@ -575,6 +741,8 @@ static int file_set_sd( struct object *obj, const struct security_descriptor *sd + *mode = st.st_mode; +@@ -580,6 +817,8 @@ int file_set_acls( struct object *obj, struct fd *fd, const struct security_desc mode = st.st_mode & (S_ISUID|S_ISGID|S_ISVTX); mode |= sd_to_mode( sd, owner ); -+ set_file_acls( unix_fd, sd ); ++ set_xattr_acls( unix_fd, sd ); + if (((st.st_mode ^ mode) & (S_IRWXU|S_IRWXG|S_IRWXO)) && fchmod( unix_fd, mode ) == -1) { file_set_error(); -diff --git a/server/file.h b/server/file.h -index 493d30b..721c087 100644 ---- a/server/file.h -+++ b/server/file.h -@@ -56,7 +56,8 @@ extern struct fd *alloc_pseudo_fd( const struct fd_ops *fd_user_ops, struct obje - unsigned int options ); - extern void set_no_fd_status( struct fd *fd, unsigned int status ); - extern struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, -- unsigned int access, unsigned int sharing, unsigned int options ); -+ unsigned int access, unsigned int sharing, unsigned int options, -+ const struct security_descriptor *sd ); - extern struct fd *create_anonymous_fd( const struct fd_ops *fd_user_ops, - int unix_fd, struct object *user, unsigned int options ); - extern struct fd *dup_fd_object( struct fd *orig, unsigned int access, unsigned int sharing, -@@ -122,6 +123,8 @@ extern struct file *create_file_for_fd_obj( struct fd *fd, unsigned int access, - extern void file_set_error(void); - extern struct security_descriptor *mode_to_sd( mode_t mode, const SID *user, const SID *group ); - extern mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner ); -+extern void set_file_acls( int fd, const struct security_descriptor *sd ); -+extern struct security_descriptor *get_file_acls( int fd, const SID *user, const SID *group ); - - /* file mapping functions */ - -- 1.7.9.5 diff --git a/patches/02-ACL_Extended_Attributes/0004-server-Inherit-security-attributes-from-parent-direc.patch b/patches/02-ACL_Extended_Attributes/0004-server-Inherit-security-attributes-from-parent-direc.patch new file mode 100644 index 00000000..67899259 --- /dev/null +++ b/patches/02-ACL_Extended_Attributes/0004-server-Inherit-security-attributes-from-parent-direc.patch @@ -0,0 +1,300 @@ +From d1b4f66da4a58f3efadcf20957ed90b30211a1cb Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Fri, 7 Feb 2014 16:02:26 -0700 +Subject: server: Inherit security attributes from parent directories on + creation. + +--- + dlls/advapi32/tests/security.c | 40 ++++++++++- + server/change.c | 2 +- + server/file.c | 146 +++++++++++++++++++++++++++++++++++++++- + server/file.h | 2 +- + 4 files changed, 184 insertions(+), 6 deletions(-) + +diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c +index ceea60e..1bf3185 100644 +--- a/dlls/advapi32/tests/security.c ++++ b/dlls/advapi32/tests/security.c +@@ -3030,10 +3030,11 @@ static void test_CreateDirectoryA(void) + ACL_SIZE_INFORMATION acl_size; + ACCESS_ALLOWED_ACE *ace; + SECURITY_ATTRIBUTES sa; ++ char tmpfile[MAX_PATH]; + char tmpdir[MAX_PATH]; ++ HANDLE token, hTemp; + struct _SID *owner; + BOOL bret = TRUE; +- HANDLE token; + DWORD error; + PACL pDacl; + +@@ -3125,6 +3126,43 @@ static void test_CreateDirectoryA(void) + ace->Mask); + } + ++ /* Test inheritance of ACLs */ ++ strcpy(tmpfile, tmpdir); ++ lstrcatA(tmpfile, "/tmpfile"); ++ hTemp = CreateFileA(tmpfile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, ++ FILE_FLAG_DELETE_ON_CLOSE, NULL); ++ error = pGetNamedSecurityInfoA(tmpfile, SE_FILE_OBJECT, ++ OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, (PSID*)&owner, ++ NULL, &pDacl, NULL, &pSD); ++ ok(error == ERROR_SUCCESS, "Failed to get permissions on file.\n"); ++ bret = pGetAclInformation(pDacl, &acl_size, sizeof(acl_size), AclSizeInformation); ++ ok(bret, "GetAclInformation failed\n"); ++ ok(acl_size.AceCount == 2, "GetAclInformation returned unexpected entry count (%d != 2).\n", ++ acl_size.AceCount); ++ if (acl_size.AceCount > 0) ++ { ++ bret = pGetAce(pDacl, 0, (VOID **)&ace); ++ ok(bret, "Inherited Failed to get Current User ACE.\n"); ++ bret = EqualSid(&ace->SidStart, user_sid); ++ ok(bret, "Inherited Current User ACE != Current User SID.\n"); ++ ok(((ACE_HEADER *)ace)->AceFlags == INHERITED_ACE, ++ "Inherited Current User ACE has unexpected flags (0x%x != 0x10)\n", ((ACE_HEADER *)ace)->AceFlags); ++ ok(ace->Mask == 0x1f01ff, "Current User ACE has unexpected mask (0x%x != 0x1f01ff)\n", ++ ace->Mask); ++ } ++ if (acl_size.AceCount > 1) ++ { ++ bret = pGetAce(pDacl, 1, (VOID **)&ace); ++ ok(bret, "Inherited Failed to get Administators Group ACE.\n"); ++ bret = EqualSid(&ace->SidStart, admin_sid); ++ ok(bret, "Inherited Administators Group ACE != Administators Group SID.\n"); ++ ok(((ACE_HEADER *)ace)->AceFlags == INHERITED_ACE, ++ "Inherited Administators Group ACE has unexpected flags (0x%x != 0x10)\n", ((ACE_HEADER *)ace)->AceFlags); ++ ok(ace->Mask == 0x1f01ff, "Administators Group ACE has unexpected mask (0x%x != 0x1f01ff)\n", ++ ace->Mask); ++ } ++ CloseHandle(hTemp); ++ + done: + HeapFree(GetProcessHeap(), 0, user); + bret = RemoveDirectoryA(tmpdir); +diff --git a/server/change.c b/server/change.c +index 0b7b979..14f37c3 100644 +--- a/server/change.c ++++ b/server/change.c +@@ -292,7 +292,7 @@ static struct security_descriptor *dir_get_sd( struct object *obj ) + assert( obj->ops == &dir_ops ); + + fd = dir_get_fd( obj ); +- sd = file_get_acls( obj, fd, &dir->mode, &dir->uid ); ++ sd = file_get_acls( obj, fd, &dir->mode, &dir->uid, TRUE ); + release_object( fd ); + return sd; + } +diff --git a/server/file.c b/server/file.c +index 0df2245..c115ff7 100644 +--- a/server/file.c ++++ b/server/file.c +@@ -242,11 +242,141 @@ void set_xattr_acls( int fd, const struct security_descriptor *sd ) + #endif + } + ++struct security_descriptor *inherit_sd( const struct security_descriptor *parent_sd, int is_dir ) ++{ ++ DWORD inheritance_mask = INHERIT_ONLY_ACE|OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE; ++ struct security_descriptor *sd = NULL; ++ const ACL *parent_dacl; ++ int present; ++ ACL *dacl; ++ ++ parent_dacl = sd_get_dacl( parent_sd, &present ); ++ if (present && parent_dacl) ++ { ++ size_t dacl_size = sizeof(ACL), ace_count = 0; ++ const ACE_HEADER *parent_ace; ++ const SID *user, *group; ++ ACE_HEADER *ace; ++ char *ptr; ++ ULONG i; ++ ++ /* Calculate the size of the DACL */ ++ parent_ace = (const ACE_HEADER *)(parent_dacl + 1); ++ for (i = 0; i < parent_dacl->AceCount; i++, parent_ace = ace_next( parent_ace )) ++ { ++ int multiplier = 1; ++ ++ if (!(parent_ace->AceFlags & inheritance_mask)) continue; ++ ++ ace_count += multiplier; ++ dacl_size += multiplier * parent_ace->AceSize; ++ } ++ if(!ace_count) return sd; /* No inheritance */ ++ ++ /* Fill in the security descriptor so that it is compatible with our DACL */ ++ user = (const SID *)(parent_sd + 1); ++ group = (const SID *)((char *)(parent_sd + 1) + parent_sd->owner_len); ++ sd = mem_alloc( sizeof(struct security_descriptor) + security_sid_len( user ) ++ + security_sid_len( group ) + dacl_size ); ++ if (!sd) return sd; ++ sd->control = SE_DACL_PRESENT; ++ sd->owner_len = parent_sd->owner_len; ++ sd->group_len = parent_sd->group_len; ++ sd->sacl_len = 0; ++ sd->dacl_len = dacl_size; ++ ptr = (char *)(sd + 1); ++ memcpy( ptr, user, sd->owner_len ); ++ ptr += sd->owner_len; ++ memcpy( ptr, group, sd->group_len ); ++ ptr += sd->group_len; ++ dacl = (ACL *)ptr; ++ dacl->AclRevision = ACL_REVISION; ++ dacl->Sbz1 = 0; ++ dacl->AclSize = dacl_size; ++ dacl->AceCount = ace_count; ++ dacl->Sbz2 = 0; ++ ace = (ACE_HEADER *)(dacl + 1); ++ ++ /* Build the new DACL, inheriting from the parent's information */ ++ parent_ace = (const ACE_HEADER *)(parent_dacl + 1); ++ for (i = 0; i < parent_dacl->AceCount; i++, parent_ace = ace_next( parent_ace )) ++ { ++ DWORD flags = parent_ace->AceFlags; ++ ++ if (!(flags & inheritance_mask)) continue; ++ ++ ace->AceType = parent_ace->AceType; ++ if(is_dir && (flags & CONTAINER_INHERIT_ACE)) ++ flags &= ~INHERIT_ONLY_ACE; ++ else if(!is_dir && (flags & OBJECT_INHERIT_ACE)) ++ flags &= ~INHERIT_ONLY_ACE; ++ else if(is_dir && (flags & OBJECT_INHERIT_ACE)) ++ flags |= INHERIT_ONLY_ACE; ++ if(is_dir) ++ ace->AceFlags = flags | INHERITED_ACE; ++ else ++ ace->AceFlags = (parent_ace->AceFlags & ~inheritance_mask) | INHERITED_ACE; ++ ace->AceSize = parent_ace->AceSize; ++ memcpy( ace + 1, parent_ace + 1, parent_ace->AceSize - sizeof(ACE_HEADER)); ++ ace = (ACE_HEADER *)ace_next( ace ); ++ } ++ } ++ return sd; ++} ++ ++static struct security_descriptor *file_get_parent_sd( struct fd *root, char *parent_name, ++ int is_dir ) ++{ ++ struct security_descriptor *sd = NULL; ++ int len = strlen( parent_name ); ++ mode_t parent_mode = 0555; ++ struct fd *parent_fd; ++ char *slash; ++ ++ /* Even if the file is a directory we need its parent, so skip any terminating slash */ ++ if (parent_name[len-1] == '/') ++ parent_name[len-1] = 0; ++ /* Find the last slash in the filename and terminate the name there */ ++ slash = strrchr(parent_name, '/'); ++ if (slash) ++ slash[0] = 0; ++ else ++ parent_name[0] = 0; ++ ++ parent_fd = open_fd( root, parent_name, O_NONBLOCK | O_LARGEFILE, &parent_mode, ++ READ_CONTROL|ACCESS_SYSTEM_SECURITY, ++ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, ++ FILE_OPEN_FOR_BACKUP_INTENT ); ++ if(parent_fd) ++ { ++ struct object *obj; ++ ++ if ((obj = create_file_obj( parent_fd, READ_CONTROL|ACCESS_SYSTEM_SECURITY, parent_mode ))) ++ { ++ struct file *file = (struct file *)obj; ++ struct fd *fd; ++ ++ fd = file_get_fd( obj ); ++ if (fd) ++ { ++ sd = file_get_acls( obj, fd, &file->mode, &file->uid, FALSE ); ++ release_object( fd ); ++ } ++ if (sd) ++ sd = inherit_sd( sd, is_dir ); ++ release_object( obj ); ++ } ++ release_object( parent_fd ); ++ } ++ return sd; ++} ++ + static struct object *create_file( struct fd *root, const char *nameptr, data_size_t len, + unsigned int access, unsigned int sharing, int create, + unsigned int options, unsigned int attrs, + const struct security_descriptor *sd ) + { ++ struct security_descriptor *temp_sd = NULL; + struct object *obj = NULL; + struct fd *fd; + int flags; +@@ -275,6 +405,15 @@ static struct object *create_file( struct fd *root, const char *nameptr, data_si + default: set_error( STATUS_INVALID_PARAMETER ); goto done; + } + ++ if (!sd && (create == FILE_CREATE || create == FILE_OVERWRITE_IF)) ++ { ++ /* Note: inheritance of security descriptors only occurs on creation when sd is NULL */ ++ char *child_name = strndup( nameptr, len ); ++ ++ sd = temp_sd = file_get_parent_sd( root, child_name, options & FILE_DIRECTORY_FILE ); ++ free(child_name); ++ } ++ + if (sd) + { + const SID *owner = sd_get_owner( sd ); +@@ -315,6 +454,7 @@ static struct object *create_file( struct fd *root, const char *nameptr, data_si + release_object( fd ); + + done: ++ free( temp_sd ); + free( name ); + return obj; + } +@@ -659,7 +799,7 @@ void convert_generic_sd( struct security_descriptor *sd ) + } + + struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mode_t *mode, +- uid_t *uid ) ++ uid_t *uid, int convert_generic ) + { + int unix_fd = get_unix_fd( fd ); + struct security_descriptor *sd; +@@ -677,7 +817,7 @@ struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mo + user = security_unix_uid_to_sid( st.st_uid ); + group = token_get_primary_group( current->process->token ); + sd = get_xattr_acls( unix_fd, user, group ); +- if (sd) convert_generic_sd( sd ); ++ if (sd && convert_generic) convert_generic_sd( sd ); + if (!sd) sd = mode_to_sd( st.st_mode, user, group); + if (!sd) return obj->sd; + +@@ -697,7 +837,7 @@ static struct security_descriptor *file_get_sd( struct object *obj ) + assert( obj->ops == &file_ops ); + + fd = file_get_fd( obj ); +- sd = file_get_acls( obj, fd, &file->mode, &file->uid ); ++ sd = file_get_acls( obj, fd, &file->mode, &file->uid, TRUE ); + release_object( fd ); + return sd; + } +diff --git a/server/file.h b/server/file.h +index 89b5888..0905fbb 100644 +--- a/server/file.h ++++ b/server/file.h +@@ -125,7 +125,7 @@ extern mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner + extern int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *sd, + unsigned int set_info ); + extern struct security_descriptor *file_get_acls( struct object *obj, struct fd *fd, mode_t *mode, +- uid_t *uid ); ++ uid_t *uid, int convert_generic ); + + /* file mapping functions */ + +-- +1.7.9.5 + diff --git a/patches/02-ACL_Extended_Attributes/0005-server-Inherit-security-attributes-from-parent-direc.patch b/patches/02-ACL_Extended_Attributes/0005-server-Inherit-security-attributes-from-parent-direc.patch new file mode 100644 index 00000000..5e157257 --- /dev/null +++ b/patches/02-ACL_Extended_Attributes/0005-server-Inherit-security-attributes-from-parent-direc.patch @@ -0,0 +1,309 @@ +From d944c7710c60e2cc1599bd6da75a41f876d647af Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Fri, 7 Feb 2014 16:03:46 -0700 +Subject: server: Inherit security attributes from parent directories on + SetSecurityInfo. + +--- + dlls/advapi32/tests/security.c | 68 +++++++++++++++++++++++ + include/winnt.h | 7 ++- + server/fd.c | 13 ++++- + server/file.c | 120 +++++++++++++++++++++++++++++++++++++++- + server/file.h | 1 + + 5 files changed, 203 insertions(+), 6 deletions(-) + +diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c +index 1bf3185..302f6b9 100644 +--- a/dlls/advapi32/tests/security.c ++++ b/dlls/advapi32/tests/security.c +@@ -3350,6 +3350,74 @@ static void test_GetNamedSecurityInfoA(void) + "Administators Group ACE has unexpected mask (0x%x != 0x1f01ff)\n", ace->Mask); + } + LocalFree(pSD); ++ CloseHandle(hTemp); ++ ++ /* Create security descriptor with no inheritance and test that it comes back the same */ ++ pSD = &sd; ++ pDacl = HeapAlloc(GetProcessHeap(), 0, 100); ++ InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION); ++ pCreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, admin_sid, &sid_size); ++ bret = InitializeAcl(pDacl, 100, ACL_REVISION); ++ ok(bret, "Failed to initialize ACL.\n"); ++ bret = pAddAccessAllowedAceEx(pDacl, ACL_REVISION, 0, GENERIC_ALL, user_sid); ++ ok(bret, "Failed to add Current User to ACL.\n"); ++ bret = pAddAccessAllowedAceEx(pDacl, ACL_REVISION, 0, GENERIC_ALL, admin_sid); ++ ok(bret, "Failed to add Administrator Group to ACL.\n"); ++ bret = SetSecurityDescriptorDacl(pSD, TRUE, pDacl, FALSE); ++ ok(bret, "Failed to add ACL to security desciptor.\n"); ++ GetTempFileNameA(".", "foo", 0, tmpfile); ++ hTemp = CreateFileA(tmpfile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, ++ FILE_FLAG_DELETE_ON_CLOSE, NULL); ++ SetLastError(0xdeadbeef); ++ error = pSetNamedSecurityInfoA(tmpfile, SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION|PROTECTED_DACL_SECURITY_INFORMATION, ++ NULL, NULL, pDacl, NULL); ++ HeapFree(GetProcessHeap(), 0, pDacl); ++ if (error != ERROR_SUCCESS && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)) ++ { ++ win_skip("SetNamedSecurityInfoA is not implemented\n"); ++ HeapFree(GetProcessHeap(), 0, user); ++ CloseHandle(hTemp); ++ return; ++ } ++ ok(!error, "SetNamedSecurityInfoA failed with error %d\n", error); ++ SetLastError(0xdeadbeef); ++ error = pGetNamedSecurityInfoA(tmpfile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ NULL, NULL, &pDacl, NULL, &pSD); ++ if (error != ERROR_SUCCESS && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)) ++ { ++ win_skip("GetNamedSecurityInfoA is not implemented\n"); ++ HeapFree(GetProcessHeap(), 0, user); ++ CloseHandle(hTemp); ++ return; ++ } ++ ok(!error, "GetNamedSecurityInfo failed with error %d\n", error); ++ ++ bret = pGetAclInformation(pDacl, &acl_size, sizeof(acl_size), AclSizeInformation); ++ ok(bret, "GetAclInformation failed\n"); ++ if (acl_size.AceCount > 0) ++ { ++ bret = pGetAce(pDacl, 0, (VOID **)&ace); ++ ok(bret, "Failed to get Current User ACE.\n"); ++ bret = EqualSid(&ace->SidStart, user_sid); ++ ok(bret, "Current User ACE != Current User SID.\n"); ++ ok(((ACE_HEADER *)ace)->AceFlags == 0, ++ "Current User ACE has unexpected flags (0x%x != 0x0)\n", ((ACE_HEADER *)ace)->AceFlags); ++ ok(ace->Mask == 0x1f01ff, "Current User ACE has unexpected mask (0x%x != 0x1f01ff)\n", ++ ace->Mask); ++ } ++ if (acl_size.AceCount > 1) ++ { ++ bret = pGetAce(pDacl, 1, (VOID **)&ace); ++ ok(bret, "Failed to get Administators Group ACE.\n"); ++ bret = EqualSid(&ace->SidStart, admin_sid); ++ ok(bret || broken(!bret) /* win2k */, "Administators Group ACE != Administators Group SID.\n"); ++ ok(((ACE_HEADER *)ace)->AceFlags == 0, ++ "Administators Group ACE has unexpected flags (0x%x != 0x0)\n", ((ACE_HEADER *)ace)->AceFlags); ++ ok(ace->Mask == 0x1f01ff || broken(ace->Mask == GENERIC_ALL) /* win2k */, ++ "Administators Group ACE has unexpected mask (0x%x != 0x1f01ff)\n", ace->Mask); ++ } ++ LocalFree(pSD); + HeapFree(GetProcessHeap(), 0, user); + CloseHandle(hTemp); + +diff --git a/include/winnt.h b/include/winnt.h +index f785d33..5151638 100644 +--- a/include/winnt.h ++++ b/include/winnt.h +@@ -5054,14 +5054,15 @@ typedef struct _TAPE_GET_MEDIA_PARAMETERS { + BOOLEAN WriteProtected; + } TAPE_GET_MEDIA_PARAMETERS, *PTAPE_GET_MEDIA_PARAMETERS; + +-/* ----------------------------- begin registry ----------------------------- */ +- +-/* Registry security values */ + #define OWNER_SECURITY_INFORMATION 0x00000001 + #define GROUP_SECURITY_INFORMATION 0x00000002 + #define DACL_SECURITY_INFORMATION 0x00000004 + #define SACL_SECURITY_INFORMATION 0x00000008 ++#define PROTECTED_DACL_SECURITY_INFORMATION 0x80000000 + ++/* ----------------------------- begin registry ----------------------------- */ ++ ++/* Registry security values */ + #define REG_OPTION_RESERVED 0x00000000 + #define REG_OPTION_NON_VOLATILE 0x00000000 + #define REG_OPTION_VOLATILE 0x00000001 +diff --git a/server/fd.c b/server/fd.c +index fa8874c..9e6b9a8 100644 +--- a/server/fd.c ++++ b/server/fd.c +@@ -1629,6 +1629,16 @@ struct fd *alloc_pseudo_fd( const struct fd_ops *fd_user_ops, struct object *use + return fd; + } + ++char *fd_get_unix_name( struct fd *obj ) ++{ ++ char *unix_name; ++ ++ unix_name = mem_alloc( strlen(obj->unix_name) + 1 ); ++ if (!unix_name) return NULL; ++ strcpy( unix_name, obj->unix_name ); ++ return unix_name; ++} ++ + /* duplicate an fd object for a different user */ + struct fd *dup_fd_object( struct fd *orig, unsigned int access, unsigned int sharing, unsigned int options ) + { +@@ -1642,8 +1652,7 @@ struct fd *dup_fd_object( struct fd *orig, unsigned int access, unsigned int sha + + if (orig->unix_name) + { +- if (!(fd->unix_name = mem_alloc( strlen(orig->unix_name) + 1 ))) goto failed; +- strcpy( fd->unix_name, orig->unix_name ); ++ if (!(fd->unix_name = fd_get_unix_name( orig ))) goto failed; + } + + if (orig->inode) +diff --git a/server/file.c b/server/file.c +index c115ff7..f6abc8c 100644 +--- a/server/file.c ++++ b/server/file.c +@@ -324,6 +324,105 @@ struct security_descriptor *inherit_sd( const struct security_descriptor *parent + return sd; + } + ++struct security_descriptor *file_combine_sds( const struct security_descriptor *parent_sd, ++ const struct security_descriptor *child_sd ) ++{ ++ size_t dacl_size = sizeof(ACL), ace_count = 0; ++ const struct security_descriptor *old_sd; ++ struct security_descriptor *sd = NULL; ++ const ACL *child_dacl, *parent_dacl; ++ int child_present, parent_present; ++ const SID *user, *group; ++ const ACE_HEADER *old_ace; ++ ACE_HEADER *ace; ++ ACL *dacl; ++ char *ptr; ++ ULONG i; ++ ++ child_dacl = sd_get_dacl( child_sd, &child_present ); ++ if (child_present && child_dacl) ++ { ++ old_ace = (const ACE_HEADER *)(child_dacl + 1); ++ for (i = 0; i < child_dacl->AceCount; i++, old_ace = ace_next( old_ace )) ++ { ++ ace_count++; ++ dacl_size += sizeof(ACE_HEADER) + old_ace->AceSize; ++ } ++ } ++ ++ parent_dacl = sd_get_dacl( parent_sd, &parent_present ); ++ if (parent_present && parent_dacl) ++ { ++ old_ace = (const ACE_HEADER *)(parent_dacl + 1); ++ for (i = 0; i < parent_dacl->AceCount; i++, old_ace = ace_next( old_ace )) ++ { ++ ace_count++; ++ dacl_size += sizeof(ACE_HEADER) + old_ace->AceSize; ++ } ++ } ++ ++ if(!ace_count) return sd; /* No inheritance */ ++ ++ if (child_present && child_dacl) ++ old_sd = child_sd; ++ else ++ old_sd = parent_sd; ++ ++ /* Fill in the security descriptor so that it is compatible with our DACL */ ++ user = (const SID *)(old_sd + 1); ++ group = (const SID *)((char *)(old_sd + 1) + old_sd->owner_len); ++ sd = mem_alloc( sizeof(struct security_descriptor) + security_sid_len( user ) ++ + security_sid_len( group ) + dacl_size ); ++ if (!sd) return sd; ++ sd->control = SE_DACL_PRESENT; ++ sd->owner_len = old_sd->owner_len; ++ sd->group_len = old_sd->group_len; ++ sd->sacl_len = 0; ++ sd->dacl_len = dacl_size; ++ ptr = (char *)(sd + 1); ++ memcpy( ptr, user, sd->owner_len ); ++ ptr += sd->owner_len; ++ memcpy( ptr, group, sd->group_len ); ++ ptr += sd->group_len; ++ dacl = (ACL *)ptr; ++ dacl->AclRevision = ACL_REVISION; ++ dacl->Sbz1 = 0; ++ dacl->AclSize = dacl_size; ++ dacl->AceCount = ace_count; ++ dacl->Sbz2 = 0; ++ ace = (ACE_HEADER *)(dacl + 1); ++ ++ if (parent_present && parent_dacl) ++ { ++ /* Build the new DACL, inheriting from the parent's information */ ++ old_ace = (const ACE_HEADER *)(parent_dacl + 1); ++ for (i = 0; i < parent_dacl->AceCount; i++, old_ace = ace_next( old_ace )) ++ { ++ ace->AceType = old_ace->AceType; ++ ace->AceFlags = old_ace->AceFlags; ++ ace->AceSize = old_ace->AceSize; ++ memcpy( ace + 1, old_ace + 1, old_ace->AceSize); ++ ace = (ACE_HEADER *)ace_next( ace ); ++ } ++ } ++ ++ if (child_present && child_dacl) ++ { ++ /* Build the new DACL, inheriting from the child's information */ ++ old_ace = (const ACE_HEADER *)(child_dacl + 1); ++ for (i = 0; i < child_dacl->AceCount; i++, old_ace = ace_next( old_ace )) ++ { ++ ace->AceType = old_ace->AceType; ++ ace->AceFlags = old_ace->AceFlags; ++ ace->AceSize = old_ace->AceSize; ++ memcpy( ace + 1, old_ace + 1, old_ace->AceSize); ++ ace = (ACE_HEADER *)ace_next( ace ); ++ } ++ } ++ ++ return sd; ++} ++ + static struct security_descriptor *file_get_parent_sd( struct fd *root, char *parent_name, + int is_dir ) + { +@@ -921,16 +1020,35 @@ mode_t sd_to_mode( const struct security_descriptor *sd, const SID *owner ) + return new_mode & ~denied_mode; + } + +-int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *sd, ++int file_set_acls( struct object *obj, struct fd *fd, const struct security_descriptor *new_sd, + unsigned int set_info ) + { ++ const struct security_descriptor *sd = new_sd; + int unix_fd = get_unix_fd( fd ); ++ char *child_name = NULL; + const SID *owner; + struct stat st; + mode_t mode; + + if (unix_fd == -1 || fstat( unix_fd, &st ) == -1) return 1; + ++ if (!(set_info & PROTECTED_DACL_SECURITY_INFORMATION)) ++ { ++ child_name = fd_get_unix_name( fd ); ++ if (child_name) ++ { ++ struct security_descriptor *parent_sd; ++ ++ parent_sd = file_get_parent_sd( NULL, child_name, S_ISDIR(st.st_mode) ); ++ free( child_name ); ++ if (parent_sd) ++ { ++ sd = file_combine_sds( parent_sd, new_sd ); ++ free( parent_sd ); ++ } ++ } ++ } ++ + if (set_info & OWNER_SECURITY_INFORMATION) + { + owner = sd_get_owner( sd ); +diff --git a/server/file.h b/server/file.h +index 0905fbb..8cbc4cb 100644 +--- a/server/file.h ++++ b/server/file.h +@@ -77,6 +77,7 @@ extern void allow_fd_caching( struct fd *fd ); + extern void set_fd_signaled( struct fd *fd, int signaled ); + extern int is_fd_signaled( struct fd *fd ); + ++extern char *fd_get_unix_name( struct fd *obj ); + extern int default_fd_signaled( struct object *obj, struct wait_queue_entry *entry ); + extern unsigned int default_fd_map_access( struct object *obj, unsigned int access ); + extern int default_fd_get_poll_events( struct fd *fd ); +-- +1.7.9.5 + diff --git a/patches/02-ACL_Extended_Attributes/0006-shell32-Set-the-default-security-attributes-for-user.patch b/patches/02-ACL_Extended_Attributes/0006-shell32-Set-the-default-security-attributes-for-user.patch new file mode 100644 index 00000000..b8cd9198 --- /dev/null +++ b/patches/02-ACL_Extended_Attributes/0006-shell32-Set-the-default-security-attributes-for-user.patch @@ -0,0 +1,139 @@ +From 14a4e501f57bd10cb55b317ff6f4d45b06c0f0cd Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Fri, 7 Feb 2014 16:04:05 -0700 +Subject: shell32: Set the default security attributes for user shell folders. + +--- + dlls/shell32/shellpath.c | 94 +++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 93 insertions(+), 1 deletion(-) + +diff --git a/dlls/shell32/shellpath.c b/dlls/shell32/shellpath.c +index 875be38..3476a88 100644 +--- a/dlls/shell32/shellpath.c ++++ b/dlls/shell32/shellpath.c +@@ -2169,6 +2169,70 @@ cleanup: + return hr; + } + ++PSECURITY_DESCRIPTOR _SHGetUserSecurityDescriptor( void ) ++{ ++ PSECURITY_DESCRIPTOR sd = HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH); ++ PACL dacl = HeapAlloc(GetProcessHeap(), 0, 100); ++ PSID admin_sid = NULL, user_sid; ++ TOKEN_USER *user = NULL; ++ BOOL ret = FALSE; ++ DWORD sid_size; ++ HANDLE token; ++ ++ if(!sd || !dacl) goto cleanup; ++ ++ /* find the user SID */ ++ ret = TRUE; ++ if (!OpenThreadToken(GetCurrentThread(), TOKEN_READ, TRUE, &token)) ++ { ++ if (GetLastError() != ERROR_NO_TOKEN) ret = FALSE; ++ else if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token)) ret = FALSE; ++ } ++ if (!ret) goto cleanup; ++ sid_size = 0; ++ ret = GetTokenInformation(token, TokenUser, NULL, 0, &sid_size); ++ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto cleanup; ++ user = HeapAlloc(GetProcessHeap(), 0, sid_size); ++ if (!user) goto cleanup; ++ ret = GetTokenInformation(token, TokenUser, user, sid_size, &sid_size); ++ if (!ret) goto cleanup; ++ CloseHandle(token); ++ user_sid = user->User.Sid; ++ ++ /* find the administrator group SID */ ++ sid_size = 0; ++ ret = CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, NULL, &sid_size); ++ if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto cleanup; ++ admin_sid = HeapAlloc(GetProcessHeap(), 0, sid_size); ++ if(!admin_sid) goto cleanup; ++ ret = CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, admin_sid, &sid_size); ++ if(!ret) goto cleanup; ++ ++ /* build the DACL */ ++ ret = InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION); ++ if(!ret) goto cleanup; ++ ret = InitializeAcl(dacl, 100, ACL_REVISION); ++ if(!ret) goto cleanup; ++ ret = AddAccessAllowedAceEx(dacl, ACL_REVISION, OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE, ++ GENERIC_ALL, user_sid); ++ if(!ret) goto cleanup; ++ ret = AddAccessAllowedAceEx(dacl, ACL_REVISION, OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE, ++ GENERIC_ALL, admin_sid); ++ if(!ret) goto cleanup; ++ ret = SetSecurityDescriptorDacl(sd, TRUE, dacl, FALSE); ++ ++cleanup: ++ HeapFree(GetProcessHeap(), 0, user); ++ HeapFree(GetProcessHeap(), 0, admin_sid); ++ if(!ret) ++ { ++ HeapFree(GetProcessHeap(), 0, dacl); ++ HeapFree(GetProcessHeap(), 0, sd); ++ sd = NULL; ++ } ++ return sd; ++} ++ + /************************************************************************* + * SHGetFolderPathAndSubDirW [SHELL32.@] + */ +@@ -2180,6 +2244,8 @@ HRESULT WINAPI SHGetFolderPathAndSubDirW( + LPCWSTR pszSubPath,/* [I] sub directory of the specified folder */ + LPWSTR pszPath) /* [O] converted path */ + { ++ SECURITY_ATTRIBUTES sa, *sec = NULL; ++ PSECURITY_DESCRIPTOR sd = NULL; + HRESULT hr; + WCHAR szBuildPath[MAX_PATH], szTemp[MAX_PATH]; + DWORD folder = nFolder & CSIDL_FOLDER_MASK; +@@ -2292,8 +2358,25 @@ HRESULT WINAPI SHGetFolderPathAndSubDirW( + goto end; + } + ++ /* build the appropriate security attributes for the directory */ ++ switch (type) ++ { ++ case CSIDL_Type_User: ++ sd = _SHGetUserSecurityDescriptor(); ++ break; ++ default: ++ break; ++ } ++ if (sd) ++ { ++ sa.nLength = sizeof(sa); ++ sa.lpSecurityDescriptor = sd; ++ sa.bInheritHandle = TRUE; ++ sec = &sa; ++ } ++ + /* create directory/directories */ +- ret = SHCreateDirectoryExW(hwndOwner, szBuildPath, NULL); ++ ret = SHCreateDirectoryExW(hwndOwner, szBuildPath, sec); + if (ret && ret != ERROR_ALREADY_EXISTS) + { + ERR("Failed to create directory %s.\n", debugstr_w(szBuildPath)); +@@ -2303,6 +2386,15 @@ HRESULT WINAPI SHGetFolderPathAndSubDirW( + + TRACE("Created missing system directory %s\n", debugstr_w(szBuildPath)); + end: ++ if (sd) ++ { ++ BOOL present, defaulted; ++ PACL dacl = NULL; ++ ++ GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted); ++ HeapFree(GetProcessHeap(), 0, dacl); ++ HeapFree(GetProcessHeap(), 0, sd); ++ } + TRACE("returning 0x%08x (final path is %s)\n", hr, debugstr_w(szBuildPath)); + return hr; + } +-- +1.7.9.5 + diff --git a/patches/02-ACL_Extended_Attributes/92938b89-506b-430a-ba50-32de8b286e56.def b/patches/02-ACL_Extended_Attributes/92938b89-506b-430a-ba50-32de8b286e56.def index 655c0648..a3a0a372 100644 --- a/patches/02-ACL_Extended_Attributes/92938b89-506b-430a-ba50-32de8b286e56.def +++ b/patches/02-ACL_Extended_Attributes/92938b89-506b-430a-ba50-32de8b286e56.def @@ -1,3 +1,3 @@ -Revision: 1 +Revision: 2 Author: Erich E. Hoover Title: Store and return security attributes with extended file attributes. diff --git a/patches/patch-list.patch b/patches/patch-list.patch index 7293989f..7a32d512 100644 --- a/patches/patch-list.patch +++ b/patches/patch-list.patch @@ -43,7 +43,7 @@ index a273502..5fa0cd5 100644 + const char *title; +} wine_patch_data[] = { + { "8a366b6d-8ad6-4581-8aa9-66a03590a57b:1", "Erich E. Hoover", "Implement SIO_ADDRESS_LIST_CHANGE." }, -+ { "92938b89-506b-430a-ba50-32de8b286e56:1", "Erich E. Hoover", "Store and return security attributes with extended file attributes." }, ++ { "92938b89-506b-430a-ba50-32de8b286e56:2", "Erich E. Hoover", "Store and return security attributes with extended file attributes." }, + { "9cb0f665-bf7c-485f-89cc-554adcdf8880:1", "Erich E. Hoover", "Allow string comparison with linguistic casing." }, + { "5d6bb7b5-ec88-4ed3-907d-9ad2173a2f88:1", "Sebastian Lackner", "Enable/disable windows when they are (un)mapped by foreign applications." }, + { "94186fff-6dbf-44d0-8eb1-2463d1608a0f:1", "Sebastian Lackner", "Update gl_drawable for embedded windows." },