1 /*
2   Copyright (c) 1990-2007 Info-ZIP.  All rights reserved.
3 
4   See the accompanying file LICENSE, version 2000-Apr-09 or later
5   (the contents of which are also included in unzip.h) for terms of use.
6   If, for some reason, all these files are missing, the Info-ZIP license
7   also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
8 */
9 /*
10 
11   Copyright (c) 1996  Scott Field (dedicated to Info-Zip group)
12 
13   Module Name:
14 
15     nt.c
16 
17   Abstract:
18 
19     This module implements WinNT security descriptor operations for the
20     Win32 Info-ZIP project.  Operation such as setting file security,
21     using/querying local and remote privileges, and queuing of operations
22     is performed here.  The contents of this module are only relevant
23     when the code is running on Windows NT, and the target volume supports
24     persistent Acl storage.
25 
26     User privileges that allow accessing certain privileged aspects of the
27     security descriptor (such as the Sacl) are only used if the user specified
28     to do so.
29 
30   Author:
31 
32     Scott Field (sfield@microsoft.com)
33 
34   Last revised:  18 Jan 97
35 
36  */
37 
38 #define WIN32_LEAN_AND_MEAN
39 #define UNZIP_INTERNAL
40 #include <windows.h>
41 #include "../unzip.h"
42 #ifdef __RSXNT__
43 #  include "../win32/rsxntwin.h"
44 #endif
45 #include "../win32/nt.h"
46 
47 
48 #ifdef NTSD_EAS         /* This file is only needed for NTSD handling */
49 
50 /* Borland C++ does not define FILE_SHARE_DELETE. Others also? */
51 #ifndef FILE_SHARE_DELETE
52 #  define FILE_SHARE_DELETE 0x00000004
53 #endif
54 
55 /* This macro definition is missing in old versions of MS' winbase.h. */
56 #ifndef InterlockedExchangePointer
57 #  define InterlockedExchangePointer(Target, Value) \
58       (PVOID)InterlockedExchange((PLONG)(Target), (LONG)(Value))
59 #endif
60 
61 
62 /* private prototypes */
63 
64 static BOOL Initialize(VOID);
65 static VOID GetRemotePrivilegesSet(CHAR *FileName, PDWORD dwRemotePrivileges);
66 static VOID InitLocalPrivileges(VOID);
67 
68 
69 volatile BOOL bInitialized = FALSE; /* module level stuff initialized? */
70 HANDLE hInitMutex = NULL;           /* prevent multiple initialization */
71 
72 BOOL g_bRestorePrivilege = FALSE;   /* for local set file security override */
73 BOOL g_bSaclPrivilege = FALSE;      /* for local set sacl operations, only when
74                                        restore privilege not present */
75 
76 /* our single cached volume capabilities structure that describes the last
77    volume root we encountered.  A single entry like this works well in the
78    zip/unzip scenario for a number of reasons:
79    1. typically one extraction path during unzip.
80    2. typically process one volume at a time during zip, and then move
81       on to the next.
82    3. no cleanup code required and no memory leaks.
83    4. simple code.
84 
85    This approach should be reworked to a linked list approach if we expect to
86    be called by many threads which are processing a variety of input/output
87    volumes, since lock contention and stale data may become a bottleneck. */
88 
89 VOLUMECAPS g_VolumeCaps;
90 CRITICAL_SECTION VolumeCapsLock;
91 
92 
Initialize(VOID)93 static BOOL Initialize(VOID)
94 {
95     HANDLE hMutex;
96     HANDLE hOldMutex;
97 
98     if (bInitialized) return TRUE;
99 
100     hMutex = CreateMutex(NULL, TRUE, NULL);
101     if(hMutex == NULL) return FALSE;
102 
103     hOldMutex = (HANDLE)InterlockedExchangePointer((void *)&hInitMutex,
104                                                    hMutex);
105 
106     if (hOldMutex != NULL) {
107         /* somebody setup the mutex already */
108         InterlockedExchangePointer((void *)&hInitMutex,
109                                    hOldMutex);
110 
111         CloseHandle(hMutex); /* close new, un-needed mutex */
112 
113         /* wait for initialization to complete and return status */
114         WaitForSingleObject(hOldMutex, INFINITE);
115         ReleaseMutex(hOldMutex);
116 
117         return bInitialized;
118     }
119 
120     if (!bInitialized) {
121         /* initialize module level resources */
122 
123         InitializeCriticalSection( &VolumeCapsLock );
124         memset(&g_VolumeCaps, 0, sizeof(VOLUMECAPS));
125 
126         InitLocalPrivileges();
127 
128         bInitialized = TRUE;
129     }
130 
131     InterlockedExchangePointer((void *)&hInitMutex,
132                                NULL);
133 
134     ReleaseMutex(hMutex); /* release correct mutex */
135 
136     CloseHandle(hMutex);  /* free the no longer needed handle resource */
137 
138     return TRUE;
139 }
140 
141 
ValidateSecurity(uch * securitydata)142 BOOL ValidateSecurity(uch *securitydata)
143 {
144     PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)securitydata;
145     PACL pAcl;
146     PSID pSid;
147     BOOL bAclPresent;
148     BOOL bDefaulted;
149 
150     if(!IsWinNT()) return TRUE; /* don't do anything if not on WinNT */
151 
152     if(!IsValidSecurityDescriptor(sd)) return FALSE;
153 
154     /* verify Dacl integrity */
155 
156     if(!GetSecurityDescriptorDacl(sd, &bAclPresent, &pAcl, &bDefaulted))
157         return FALSE;
158 
159     if(bAclPresent && pAcl!=NULL) {
160         if(!IsValidAcl(pAcl)) return FALSE;
161     }
162 
163     /* verify Sacl integrity */
164 
165     if(!GetSecurityDescriptorSacl(sd, &bAclPresent, &pAcl, &bDefaulted))
166         return FALSE;
167 
168     if(bAclPresent && pAcl!=NULL) {
169         if(!IsValidAcl(pAcl)) return FALSE;
170     }
171 
172     /* verify owner integrity */
173 
174     if(!GetSecurityDescriptorOwner(sd, &pSid, &bDefaulted))
175         return FALSE;
176 
177     if(pSid != NULL) {
178         if(!IsValidSid(pSid)) return FALSE;
179     }
180 
181     /* verify group integrity */
182 
183     if(!GetSecurityDescriptorGroup(sd, &pSid, &bDefaulted))
184         return FALSE;
185 
186     if(pSid != NULL) {
187         if(!IsValidSid(pSid)) return FALSE;
188     }
189 
190     return TRUE;
191 }
192 
GetRemotePrivilegesSet(char * FileName,PDWORD dwRemotePrivileges)193 static VOID GetRemotePrivilegesSet(char *FileName, PDWORD dwRemotePrivileges)
194 {
195     HANDLE hFile;
196 
197     *dwRemotePrivileges = 0;
198 
199     /* see if we have the SeRestorePrivilege */
200 
201     hFile = CreateFileA(
202         FileName,
203         ACCESS_SYSTEM_SECURITY | WRITE_DAC | WRITE_OWNER | READ_CONTROL,
204         FILE_SHARE_READ | FILE_SHARE_DELETE, /* no sd updating allowed here */
205         NULL,
206         OPEN_EXISTING,
207         FILE_FLAG_BACKUP_SEMANTICS,
208         NULL
209         );
210 
211     if(hFile != INVALID_HANDLE_VALUE) {
212         /* no remote way to determine SeRestorePrivilege -- just try a
213            read/write to simulate it */
214         SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION |
215           SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION |
216           GROUP_SECURITY_INFORMATION;
217         PSECURITY_DESCRIPTOR sd;
218         DWORD cbBuf = 0;
219 
220         GetKernelObjectSecurity(hFile, si, NULL, cbBuf, &cbBuf);
221 
222         if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
223             if((sd = HeapAlloc(GetProcessHeap(), 0, cbBuf)) != NULL) {
224                 if(GetKernelObjectSecurity(hFile, si, sd, cbBuf, &cbBuf)) {
225                     if(SetKernelObjectSecurity(hFile, si, sd))
226                         *dwRemotePrivileges |= OVERRIDE_RESTORE;
227                 }
228                 HeapFree(GetProcessHeap(), 0, sd);
229             }
230         }
231 
232         CloseHandle(hFile);
233     } else {
234 
235         /* see if we have the SeSecurityPrivilege */
236         /* note we don't need this if we have SeRestorePrivilege */
237 
238         hFile = CreateFileA(
239             FileName,
240             ACCESS_SYSTEM_SECURITY,
241             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* max */
242             NULL,
243             OPEN_EXISTING,
244             0,
245             NULL
246             );
247 
248         if(hFile != INVALID_HANDLE_VALUE) {
249             CloseHandle(hFile);
250             *dwRemotePrivileges |= OVERRIDE_SACL;
251         }
252     }
253 }
254 
255 
GetVolumeCaps(char * rootpath,char * name,PVOLUMECAPS VolumeCaps)256 BOOL GetVolumeCaps(
257     char *rootpath,         /* filepath, or NULL */
258     char *name,             /* filename associated with rootpath */
259     PVOLUMECAPS VolumeCaps  /* result structure describing capabilities */
260     )
261 {
262     char TempRootPath[MAX_PATH + 1];
263     DWORD cchTempRootPath = 0;
264     BOOL bSuccess = TRUE;   /* assume success until told otherwise */
265 
266     if(!bInitialized) if(!Initialize()) return FALSE;
267 
268     /* process the input path to produce a consistent path suitable for
269        compare operations and also suitable for certain picky Win32 API
270        that don't like forward slashes */
271 
272     if(rootpath != NULL && rootpath[0] != '\0') {
273         DWORD i;
274 
275         cchTempRootPath = lstrlenA(rootpath);
276         if(cchTempRootPath > MAX_PATH) return FALSE;
277 
278         /* copy input, converting forward slashes to back slashes as we go */
279 
280         for(i = 0 ; i <= cchTempRootPath ; i++) {
281             if(rootpath[i] == '/') TempRootPath[i] = '\\';
282             else TempRootPath[i] = rootpath[i];
283         }
284 
285         /* check for UNC and Null terminate or append trailing \ as
286            appropriate */
287 
288         /* possible valid UNCs we are passed follow:
289            \\machine\foo\bar (path is \\machine\foo\)
290            \\machine\foo     (path is \\machine\foo\)
291            \\machine\foo\
292            \\.\c$\     (FIXFIX: Win32API doesn't like this - GetComputerName())
293            LATERLATER: handling mounted DFS drives in the future will require
294                        slightly different logic which isn't available today.
295                        This is required because directories can point at
296                        different servers which have differing capabilities.
297          */
298 
299         if(TempRootPath[0] == '\\' && TempRootPath[1] == '\\') {
300             DWORD slash = 0;
301 
302             for(i = 2 ; i < cchTempRootPath ; i++) {
303                 if(TempRootPath[i] == '\\') {
304                     slash++;
305 
306                     if(slash == 2) {
307                         i++;
308                         TempRootPath[i] = '\0';
309                         cchTempRootPath = i;
310                         break;
311                     }
312                 }
313             }
314 
315             /* if there was only one slash found, just tack another onto the
316                end */
317 
318             if(slash == 1 && TempRootPath[cchTempRootPath] != '\\') {
319                 TempRootPath[cchTempRootPath] = TempRootPath[0]; /* '\\' */
320                 TempRootPath[cchTempRootPath+1] = '\0';
321                 cchTempRootPath++;
322             }
323 
324         } else {
325 
326             if(TempRootPath[1] == ':') {
327 
328                 /* drive letter specified, truncate to root */
329                 TempRootPath[2] = '\\';
330                 TempRootPath[3] = '\0';
331                 cchTempRootPath = 3;
332             } else {
333 
334                 /* must be file on current drive */
335                 TempRootPath[0] = '\0';
336                 cchTempRootPath = 0;
337             }
338 
339         }
340 
341     } /* if path != NULL */
342 
343     /* grab lock protecting cached entry */
344     EnterCriticalSection( &VolumeCapsLock );
345 
346     if(!g_VolumeCaps.bValid ||
347        lstrcmpiA(g_VolumeCaps.RootPath, TempRootPath) != 0)
348     {
349 
350         /* no match found, build up new entry */
351 
352         DWORD dwFileSystemFlags;
353         DWORD dwRemotePrivileges = 0;
354         BOOL bRemote = FALSE;
355 
356         /* release lock during expensive operations */
357         LeaveCriticalSection( &VolumeCapsLock );
358 
359         bSuccess = GetVolumeInformationA(
360             (TempRootPath[0] == '\0') ? NULL : TempRootPath,
361             NULL, 0,
362             NULL, NULL,
363             &dwFileSystemFlags,
364             NULL, 0);
365 
366 
367         /* only if target volume supports Acls, and we were told to use
368            privileges do we need to go out and test for the remote case */
369 
370         if(bSuccess && (dwFileSystemFlags & FS_PERSISTENT_ACLS) &&
371            VolumeCaps->bUsePrivileges)
372         {
373             if(GetDriveTypeA( (TempRootPath[0] == '\0') ? NULL : TempRootPath )
374                == DRIVE_REMOTE)
375             {
376                 bRemote = TRUE;
377 
378                 /* make a determination about our remote capabilities */
379 
380                 GetRemotePrivilegesSet(name, &dwRemotePrivileges);
381             }
382         }
383 
384         /* always take the lock again, since we release it below */
385         EnterCriticalSection( &VolumeCapsLock );
386 
387         /* replace the existing data if successful */
388         if(bSuccess) {
389 
390             lstrcpynA(g_VolumeCaps.RootPath, TempRootPath, cchTempRootPath+1);
391             g_VolumeCaps.dwFileSystemFlags = dwFileSystemFlags;
392             g_VolumeCaps.bRemote = bRemote;
393             g_VolumeCaps.dwRemotePrivileges = dwRemotePrivileges;
394             g_VolumeCaps.bValid = TRUE;
395         }
396     }
397 
398     if(bSuccess) {
399         /* copy input elements */
400         g_VolumeCaps.bUsePrivileges = VolumeCaps->bUsePrivileges;
401         g_VolumeCaps.dwFileAttributes = VolumeCaps->dwFileAttributes;
402 
403         /* give caller results */
404         memcpy(VolumeCaps, &g_VolumeCaps, sizeof(VOLUMECAPS));
405     } else {
406         g_VolumeCaps.bValid = FALSE;
407     }
408 
409     LeaveCriticalSection( &VolumeCapsLock ); /* release lock */
410 
411     return bSuccess;
412 }
413 
414 
SecuritySet(char * resource,PVOLUMECAPS VolumeCaps,uch * securitydata)415 BOOL SecuritySet(char *resource, PVOLUMECAPS VolumeCaps, uch *securitydata)
416 {
417     HANDLE hFile;
418     DWORD dwDesiredAccess = 0;
419     DWORD dwFlags = 0;
420     PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)securitydata;
421     SECURITY_DESCRIPTOR_CONTROL sdc;
422     SECURITY_INFORMATION RequestedInfo = 0;
423     DWORD dwRev;
424     BOOL bRestorePrivilege = FALSE;
425     BOOL bSaclPrivilege = FALSE;
426     BOOL bSuccess;
427 
428     if(!bInitialized) if(!Initialize()) return FALSE;
429 
430     /* defer directory processing */
431 
432     if(VolumeCaps->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
433         /* opening a directory requires FILE_FLAG_BACKUP_SEMANTICS */
434         dwFlags |= FILE_FLAG_BACKUP_SEMANTICS;
435     }
436 
437     /* evaluate the input security descriptor and act accordingly */
438 
439     if(!IsValidSecurityDescriptor(sd))
440         return FALSE;
441 
442     if(!GetSecurityDescriptorControl(sd, &sdc, &dwRev))
443         return FALSE;
444 
445     /* setup privilege usage based on if told we can use privileges, and if so,
446        what privileges we have */
447 
448     if(VolumeCaps->bUsePrivileges) {
449         if(VolumeCaps->bRemote) {
450             /* use remotely determined privileges */
451             if(VolumeCaps->dwRemotePrivileges & OVERRIDE_RESTORE)
452                 bRestorePrivilege = TRUE;
453 
454             if(VolumeCaps->dwRemotePrivileges & OVERRIDE_SACL)
455                 bSaclPrivilege = TRUE;
456 
457         } else {
458             /* use local privileges */
459             bRestorePrivilege = g_bRestorePrivilege;
460             bSaclPrivilege = g_bSaclPrivilege;
461         }
462     }
463 
464 
465     /* if a Dacl is present write Dacl out */
466     /* if we have SeRestorePrivilege, write owner and group info out */
467 
468     if(sdc & SE_DACL_PRESENT) {
469         dwDesiredAccess |= WRITE_DAC;
470         RequestedInfo |= DACL_SECURITY_INFORMATION;
471 
472         if(bRestorePrivilege) {
473             dwDesiredAccess |= WRITE_OWNER;
474             RequestedInfo |= (OWNER_SECURITY_INFORMATION |
475               GROUP_SECURITY_INFORMATION);
476         }
477     }
478 
479     /* if a Sacl is present and we have either SeRestorePrivilege or
480        SeSystemSecurityPrivilege try to write Sacl out */
481 
482     if((sdc & SE_SACL_PRESENT) && (bRestorePrivilege || bSaclPrivilege)) {
483         dwDesiredAccess |= ACCESS_SYSTEM_SECURITY;
484         RequestedInfo |= SACL_SECURITY_INFORMATION;
485     }
486 
487     if(RequestedInfo == 0)  /* nothing to do */
488         return FALSE;
489 
490     if(bRestorePrivilege)
491         dwFlags |= FILE_FLAG_BACKUP_SEMANTICS;
492 
493     hFile = CreateFileA(
494         resource,
495         dwDesiredAccess,
496         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,/* max sharing */
497         NULL,
498         OPEN_EXISTING,
499         dwFlags,
500         NULL
501         );
502 
503     if(hFile == INVALID_HANDLE_VALUE)
504         return FALSE;
505 
506     bSuccess = SetKernelObjectSecurity(hFile, RequestedInfo, sd);
507 
508     CloseHandle(hFile);
509 
510     return bSuccess;
511 }
512 
InitLocalPrivileges(VOID)513 static VOID InitLocalPrivileges(VOID)
514 {
515     HANDLE hToken;
516     TOKEN_PRIVILEGES tp;
517 
518     /* try to enable some interesting privileges that give us the ability
519        to get some security information that we normally cannot.
520 
521        note that enabling privileges is only relevant on the local machine;
522        when accessing files that are on a remote machine, any privileges
523        that are present on the remote machine get enabled by default. */
524 
525     if(!OpenProcessToken(GetCurrentProcess(),
526         TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
527         return;
528 
529     tp.PrivilegeCount = 1;
530     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
531 
532     if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tp.Privileges[0].Luid)) {
533 
534         /* try to enable SeRestorePrivilege; if this succeeds, we can write
535            all aspects of the security descriptor */
536 
537         if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) &&
538            GetLastError() == ERROR_SUCCESS) g_bRestorePrivilege = TRUE;
539 
540     }
541 
542     /* try to enable SeSystemSecurityPrivilege, if SeRestorePrivilege not
543        present; if this succeeds, we can write the Sacl */
544 
545     if(!g_bRestorePrivilege &&
546         LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) {
547 
548         if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) &&
549            GetLastError() == ERROR_SUCCESS) g_bSaclPrivilege = TRUE;
550     }
551 
552     CloseHandle(hToken);
553 }
554 #endif /* NTSD_EAS */
555