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 "../unzip.h" 41#include <windows.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 64static BOOL Initialize(VOID); 65static VOID GetRemotePrivilegesSet(CHAR *FileName, PDWORD dwRemotePrivileges); 66static VOID InitLocalPrivileges(VOID); 67 68 69volatile BOOL bInitialized = FALSE; /* module level stuff initialized? */ 70HANDLE hInitMutex = NULL; /* prevent multiple initialization */ 71 72BOOL g_bRestorePrivilege = FALSE; /* for local set file security override */ 73BOOL 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 89VOLUMECAPS g_VolumeCaps; 90CRITICAL_SECTION VolumeCapsLock; 91 92 93static 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 142BOOL 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 193static 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 256BOOL 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 415BOOL 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 513static 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