3 Copyright (c) 1996 Scott Field
11 This module implements WinNT security descriptor operations for the
12 Win32 Info-ZIP project. Operation such as setting file security,
13 using/querying local and remote privileges, and queuing of operations
14 is performed here. The contents of this module are only relevant
15 when the code is running on Windows NT, and the target volume supports
16 persistent Acl storage.
18 User privileges that allow accessing certain privileged aspects of the
19 security descriptor (such as the Sacl) are only used if the user specified
24 Scott Field (sfield@microsoft.com)
26 Last revised: 18 Jan 97
30 #define WIN32_LEAN_AND_MEAN
31 #define UNZIP_INTERNAL
35 # include "rsxntwin.h"
40 #ifdef NTSD_EAS /* This file is only needed for NTSD handling */
42 /* Borland C++ does not define FILE_SHARE_DELETE. Others also? */
43 #ifndef FILE_SHARE_DELETE
44 # define FILE_SHARE_DELETE 0x00000004
48 /* private prototypes */
50 static BOOL
Initialize(VOID
);
51 #if 0 /* currently unused */
52 static BOOL
Shutdown(VOID
);
54 static BOOL
DeferSet(char *resource
, PVOLUMECAPS VolumeCaps
, uch
*buffer
);
55 static VOID
GetRemotePrivilegesSet(CHAR
*FileName
, PDWORD dwRemotePrivileges
);
56 static VOID
InitLocalPrivileges(VOID
);
59 BOOL bInitialized
= FALSE
; /* module level stuff initialized? */
60 HANDLE hInitMutex
= NULL
; /* prevent multiple initialization */
62 BOOL g_bRestorePrivilege
= FALSE
; /* for local set file security override */
63 BOOL g_bSaclPrivilege
= FALSE
; /* for local set sacl operations, only when
64 restore privilege not present */
66 /* our single cached volume capabilities structure that describes the last
67 volume root we encountered. A single entry like this works well in the
68 zip/unzip scenario for a number of reasons:
69 1. typically one extraction path during unzip.
70 2. typically process one volume at a time during zip, and then move
72 3. no cleanup code required and no memory leaks.
75 This approach should be reworked to a linked list approach if we expect to
76 be called by many threads which are processing a variety of input/output
77 volumes, since lock contention and stale data may become a bottleneck. */
79 VOLUMECAPS g_VolumeCaps
;
80 CRITICAL_SECTION VolumeCapsLock
;
83 /* our deferred set structure linked list element, used for making a copy
84 of input data which is used at a later time to process the original input
85 at a time when it makes more sense. eg, applying security to newly created
86 directories, after all files have been placed in such directories. */
88 CRITICAL_SECTION SetDeferLock
;
90 typedef struct _DEFERRED_SET
{
91 struct _DEFERRED_SET
*Next
;
92 uch
*buffer
; /* must point to DWORD aligned block */
93 PVOLUMECAPS VolumeCaps
;
95 } DEFERRED_SET
, *PDEFERRED_SET
, *LPDEFERRED_SET
;
97 PDEFERRED_SET pSetHead
= NULL
;
98 PDEFERRED_SET pSetTail
;
100 static BOOL
Initialize(VOID
)
105 if(bInitialized
) return TRUE
;
107 hMutex
= CreateMutex(NULL
, TRUE
, NULL
);
108 if(hMutex
== NULL
) return FALSE
;
110 hOldMutex
= (HANDLE
)InterlockedExchange((LPLONG
)&hInitMutex
, (LONG
)hMutex
);
112 if(hOldMutex
!= NULL
) {
113 /* somebody setup the mutex already */
114 InterlockedExchange((LPLONG
)&hInitMutex
, (LONG
)hOldMutex
);
116 CloseHandle(hMutex
); /* close new, un-needed mutex */
118 /* wait for initialization to complete and return status */
119 WaitForSingleObject(hOldMutex
, INFINITE
);
120 ReleaseMutex(hOldMutex
);
125 /* initialize module level resources */
127 InitializeCriticalSection( &SetDeferLock
);
129 InitializeCriticalSection( &VolumeCapsLock
);
130 memset(&g_VolumeCaps
, 0, sizeof(VOLUMECAPS
));
132 InitLocalPrivileges();
136 ReleaseMutex(hMutex
); /* release correct mutex */
141 #if 0 /* currently not used ! */
142 static BOOL
Shutdown(VOID
)
144 /* really need to free critical sections, disable enabled privilges, etc,
145 but doing so brings up possibility of race conditions if those resources
146 are about to be used. The easiest way to handle this is let these
147 resources be freed when the process terminates... */
154 static BOOL
DeferSet(char *resource
, PVOLUMECAPS VolumeCaps
, uch
*buffer
)
161 if(!bInitialized
) if(!Initialize()) return FALSE
;
163 cbResource
= lstrlenA(resource
) + 1;
164 cbBuffer
= GetSecurityDescriptorLength((PSECURITY_DESCRIPTOR
)buffer
);
165 cbDeferSet
= sizeof(DEFERRED_SET
) + cbBuffer
+ sizeof(VOLUMECAPS
) +
168 psd
= (PDEFERRED_SET
)HeapAlloc(GetProcessHeap(), 0, cbDeferSet
);
169 if(psd
== NULL
) return FALSE
;
172 psd
->buffer
= (uch
*)(psd
+1);
173 psd
->VolumeCaps
= (PVOLUMECAPS
)((char *)psd
->buffer
+ cbBuffer
);
174 psd
->resource
= (char *)((char *)psd
->VolumeCaps
+ sizeof(VOLUMECAPS
));
176 memcpy(psd
->buffer
, buffer
, cbBuffer
);
177 memcpy(psd
->VolumeCaps
, VolumeCaps
, sizeof(VOLUMECAPS
));
178 psd
->VolumeCaps
->bProcessDefer
= TRUE
;
179 memcpy(psd
->resource
, resource
, cbResource
);
181 /* take defer lock */
182 EnterCriticalSection( &SetDeferLock
);
184 /* add element at tail of list */
186 if(pSetHead
== NULL
) {
189 pSetTail
->Next
= psd
;
194 /* release defer lock */
195 LeaveCriticalSection( &SetDeferLock
);
200 BOOL
ProcessDefer(PDWORD dwDirectoryCount
, PDWORD dwBytesProcessed
,
201 PDWORD dwDirectoryFail
, PDWORD dwBytesFail
)
206 *dwDirectoryCount
= 0;
207 *dwBytesProcessed
= 0;
209 *dwDirectoryFail
= 0;
212 if(!bInitialized
) return TRUE
; /* nothing to do */
214 EnterCriticalSection( &SetDeferLock
);
220 if(SecuritySet(This
->resource
, This
->VolumeCaps
, This
->buffer
)) {
221 (*dwDirectoryCount
)++;
223 GetSecurityDescriptorLength((PSECURITY_DESCRIPTOR
)This
->buffer
);
225 (*dwDirectoryFail
)++;
227 GetSecurityDescriptorLength((PSECURITY_DESCRIPTOR
)This
->buffer
);
231 HeapFree(GetProcessHeap(), 0, This
);
237 LeaveCriticalSection( &SetDeferLock
);
242 BOOL
ValidateSecurity(uch
*securitydata
)
244 PSECURITY_DESCRIPTOR sd
= (PSECURITY_DESCRIPTOR
)securitydata
;
250 if(!IsWinNT()) return TRUE
; /* don't do anything if not on WinNT */
252 if(!IsValidSecurityDescriptor(sd
)) return FALSE
;
254 /* verify Dacl integrity */
256 if(!GetSecurityDescriptorDacl(sd
, &bAclPresent
, &pAcl
, &bDefaulted
))
260 if(!IsValidAcl(pAcl
)) return FALSE
;
263 /* verify Sacl integrity */
265 if(!GetSecurityDescriptorSacl(sd
, &bAclPresent
, &pAcl
, &bDefaulted
))
269 if(!IsValidAcl(pAcl
)) return FALSE
;
272 /* verify owner integrity */
274 if(!GetSecurityDescriptorOwner(sd
, &pSid
, &bDefaulted
))
278 if(!IsValidSid(pSid
)) return FALSE
;
281 /* verify group integrity */
283 if(!GetSecurityDescriptorGroup(sd
, &pSid
, &bDefaulted
))
287 if(!IsValidSid(pSid
)) return FALSE
;
293 static VOID
GetRemotePrivilegesSet(char *FileName
, PDWORD dwRemotePrivileges
)
297 *dwRemotePrivileges
= 0;
299 /* see if we have the SeRestorePrivilege */
303 ACCESS_SYSTEM_SECURITY
| WRITE_DAC
| WRITE_OWNER
| READ_CONTROL
,
304 FILE_SHARE_READ
| FILE_SHARE_DELETE
, /* no sd updating allowed here */
307 FILE_FLAG_BACKUP_SEMANTICS
,
311 if(hFile
!= INVALID_HANDLE_VALUE
) {
312 /* no remote way to determine SeRestorePrivilege -- just try a
313 read/write to simulate it */
314 SECURITY_INFORMATION si
= DACL_SECURITY_INFORMATION
|
315 SACL_SECURITY_INFORMATION
| OWNER_SECURITY_INFORMATION
|
316 GROUP_SECURITY_INFORMATION
;
317 PSECURITY_DESCRIPTOR sd
;
320 GetKernelObjectSecurity(hFile
, si
, NULL
, cbBuf
, &cbBuf
);
322 if(ERROR_INSUFFICIENT_BUFFER
== GetLastError()) {
323 if((sd
= HeapAlloc(GetProcessHeap(), 0, cbBuf
)) != NULL
) {
324 if(GetKernelObjectSecurity(hFile
, si
, sd
, cbBuf
, &cbBuf
)) {
325 if(SetKernelObjectSecurity(hFile
, si
, sd
))
326 *dwRemotePrivileges
|= OVERRIDE_RESTORE
;
328 HeapFree(GetProcessHeap(), 0, sd
);
335 /* see if we have the SeSecurityPrivilege */
336 /* note we don't need this if we have SeRestorePrivilege */
340 ACCESS_SYSTEM_SECURITY
,
341 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, /* max */
348 if(hFile
!= INVALID_HANDLE_VALUE
) {
350 *dwRemotePrivileges
|= OVERRIDE_SACL
;
357 char *rootpath
, /* filepath, or NULL */
358 char *name
, /* filename associated with rootpath */
359 PVOLUMECAPS VolumeCaps
/* result structure describing capabilities */
362 char TempRootPath
[MAX_PATH
+ 1];
363 DWORD cchTempRootPath
= 0;
364 BOOL bSuccess
= TRUE
; /* assume success until told otherwise */
366 if(!bInitialized
) if(!Initialize()) return FALSE
;
368 /* process the input path to produce a consistent path suitable for
369 compare operations and also suitable for certain picky Win32 API
370 that don't like forward slashes */
372 if(rootpath
!= NULL
&& rootpath
[0] != '\0') {
375 cchTempRootPath
= lstrlen(rootpath
);
376 if(cchTempRootPath
> MAX_PATH
) return FALSE
;
378 /* copy input, converting forward slashes to back slashes as we go */
380 for(i
= 0 ; i
<= cchTempRootPath
; i
++) {
381 if(rootpath
[i
] == '/') TempRootPath
[i
] = '\\';
382 else TempRootPath
[i
] = rootpath
[i
];
385 /* check for UNC and Null terminate or append trailing \ as
388 /* possible valid UNCs we are passed follow:
389 \\machine\foo\bar (path is \\machine\foo\)
390 \\machine\foo (path is \\machine\foo\)
392 \\.\c$\ (FIXFIX: Win32API doesn't like this - GetComputerName())
393 LATERLATER: handling mounted DFS drives in the future will require
394 slightly different logic which isn't available today.
395 This is required because directories can point at
396 different servers which have differing capabilities.
399 if(TempRootPath
[0] == '\\' && TempRootPath
[1] == '\\') {
402 for(i
= 2 ; i
< cchTempRootPath
; i
++) {
403 if(TempRootPath
[i
] == '\\') {
408 TempRootPath
[i
] = '\0';
415 /* if there was only one slash found, just tack another onto the
418 if(slash
== 1 && TempRootPath
[cchTempRootPath
] != '\\') {
419 TempRootPath
[cchTempRootPath
] = TempRootPath
[0]; /* '\' */
420 TempRootPath
[cchTempRootPath
+1] = '\0';
426 if(TempRootPath
[1] == ':') {
428 /* drive letter specified, truncate to root */
429 TempRootPath
[2] = '\\';
430 TempRootPath
[3] = '\0';
434 /* must be file on current drive */
435 TempRootPath
[0] = '\0';
441 } /* if path != NULL */
443 /* grab lock protecting cached entry */
444 EnterCriticalSection( &VolumeCapsLock
);
446 if(!g_VolumeCaps
.bValid
||
447 lstrcmpi(g_VolumeCaps
.RootPath
, TempRootPath
) != 0)
450 /* no match found, build up new entry */
452 DWORD dwFileSystemFlags
;
453 DWORD dwRemotePrivileges
= 0;
454 BOOL bRemote
= FALSE
;
456 /* release lock during expensive operations */
457 LeaveCriticalSection( &VolumeCapsLock
);
459 bSuccess
= GetVolumeInformation(
460 (TempRootPath
[0] == '\0') ? NULL
: TempRootPath
,
467 /* only if target volume supports Acls, and we were told to use
468 privileges do we need to go out and test for the remote case */
470 if(bSuccess
&& (dwFileSystemFlags
& FS_PERSISTENT_ACLS
) &&
471 VolumeCaps
->bUsePrivileges
)
473 if(GetDriveType( (TempRootPath
[0] == '\0') ? NULL
: TempRootPath
)
478 /* make a determination about our remote capabilities */
480 GetRemotePrivilegesSet(name
, &dwRemotePrivileges
);
484 /* always take the lock again, since we release it below */
485 EnterCriticalSection( &VolumeCapsLock
);
487 /* replace the existing data if successful */
490 lstrcpynA(g_VolumeCaps
.RootPath
, TempRootPath
, cchTempRootPath
+1);
491 g_VolumeCaps
.bProcessDefer
= FALSE
;
492 g_VolumeCaps
.dwFileSystemFlags
= dwFileSystemFlags
;
493 g_VolumeCaps
.bRemote
= bRemote
;
494 g_VolumeCaps
.dwRemotePrivileges
= dwRemotePrivileges
;
495 g_VolumeCaps
.bValid
= TRUE
;
500 /* copy input elements */
501 g_VolumeCaps
.bUsePrivileges
= VolumeCaps
->bUsePrivileges
;
502 g_VolumeCaps
.dwFileAttributes
= VolumeCaps
->dwFileAttributes
;
504 /* give caller results */
505 memcpy(VolumeCaps
, &g_VolumeCaps
, sizeof(VOLUMECAPS
));
507 g_VolumeCaps
.bValid
= FALSE
;
510 LeaveCriticalSection( &VolumeCapsLock
); /* release lock */
516 BOOL
SecuritySet(char *resource
, PVOLUMECAPS VolumeCaps
, uch
*securitydata
)
519 DWORD dwDesiredAccess
= 0;
521 PSECURITY_DESCRIPTOR sd
= (PSECURITY_DESCRIPTOR
)securitydata
;
522 SECURITY_DESCRIPTOR_CONTROL sdc
;
523 SECURITY_INFORMATION RequestedInfo
= 0;
525 BOOL bRestorePrivilege
= FALSE
;
526 BOOL bSaclPrivilege
= FALSE
;
529 if(!bInitialized
) if(!Initialize()) return FALSE
;
531 /* defer directory processing */
533 if(VolumeCaps
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
534 if(!VolumeCaps
->bProcessDefer
) {
535 return DeferSet(resource
, VolumeCaps
, securitydata
);
537 /* opening a directory requires FILE_FLAG_BACKUP_SEMANTICS */
538 dwFlags
|= FILE_FLAG_BACKUP_SEMANTICS
;
542 /* evaluate the input security desriptor and act accordingly */
544 if(!IsValidSecurityDescriptor(sd
))
547 if(!GetSecurityDescriptorControl(sd
, &sdc
, &dwRev
))
550 /* setup privilege usage based on if told we can use privileges, and if so,
551 what privileges we have */
553 if(VolumeCaps
->bUsePrivileges
) {
554 if(VolumeCaps
->bRemote
) {
555 /* use remotely determined privileges */
556 if(VolumeCaps
->dwRemotePrivileges
& OVERRIDE_RESTORE
)
557 bRestorePrivilege
= TRUE
;
559 if(VolumeCaps
->dwRemotePrivileges
& OVERRIDE_SACL
)
560 bSaclPrivilege
= TRUE
;
563 /* use local privileges */
564 bRestorePrivilege
= g_bRestorePrivilege
;
565 bSaclPrivilege
= g_bSaclPrivilege
;
570 /* if a Dacl is present write Dacl out */
571 /* if we have SeRestorePrivilege, write owner and group info out */
573 if(sdc
& SE_DACL_PRESENT
) {
574 dwDesiredAccess
|= WRITE_DAC
;
575 RequestedInfo
|= DACL_SECURITY_INFORMATION
;
577 if(bRestorePrivilege
) {
578 dwDesiredAccess
|= WRITE_OWNER
;
579 RequestedInfo
|= (OWNER_SECURITY_INFORMATION
|
580 GROUP_SECURITY_INFORMATION
);
584 /* if a Sacl is present and we have either SeRestorePrivilege or
585 SeSystemSecurityPrivilege try to write Sacl out */
587 if((sdc
& SE_SACL_PRESENT
) && (bRestorePrivilege
|| bSaclPrivilege
)) {
588 dwDesiredAccess
|= ACCESS_SYSTEM_SECURITY
;
589 RequestedInfo
|= SACL_SECURITY_INFORMATION
;
592 if(RequestedInfo
== 0) /* nothing to do */
595 if(bRestorePrivilege
)
596 dwFlags
|= FILE_FLAG_BACKUP_SEMANTICS
;
601 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,/* max sharing */
608 if(hFile
== INVALID_HANDLE_VALUE
)
611 bSuccess
= SetKernelObjectSecurity(hFile
, RequestedInfo
, sd
);
618 static VOID
InitLocalPrivileges(VOID
)
623 /* try to enable some interesting privileges that give us the ability
624 to get some security information that we normally cannot.
626 note that enabling privileges is only relevant on the local machine;
627 when accessing files that are on a remote machine, any privileges
628 that are present on the remote machine get enabled by default. */
630 if(!OpenProcessToken(GetCurrentProcess(),
631 TOKEN_QUERY
| TOKEN_ADJUST_PRIVILEGES
, &hToken
))
634 tp
.PrivilegeCount
= 1;
635 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
637 if(LookupPrivilegeValue(NULL
, SE_RESTORE_NAME
, &tp
.Privileges
[0].Luid
)) {
639 /* try to enable SeRestorePrivilege; if this succeeds, we can write
640 all aspects of the security descriptor */
642 if(AdjustTokenPrivileges(hToken
, FALSE
, &tp
, 0, NULL
, NULL
) &&
643 GetLastError() == ERROR_SUCCESS
) g_bRestorePrivilege
= TRUE
;
647 /* try to enable SeSystemSecurityPrivilege, if SeRestorePrivilege not
648 present; if this succeeds, we can write the Sacl */
650 if(!g_bRestorePrivilege
&&
651 LookupPrivilegeValue(NULL
, SE_SECURITY_NAME
, &tp
.Privileges
[0].Luid
)) {
653 if(AdjustTokenPrivileges(hToken
, FALSE
, &tp
, 0, NULL
, NULL
) &&
654 GetLastError() == ERROR_SUCCESS
) g_bSaclPrivilege
= TRUE
;
659 #endif /* NTSD_EAS */