@@ -2248,13 +2248,315 @@ static void fs__access(uv_fs_t* req) {
2248
2248
SET_REQ_SUCCESS (req );
2249
2249
}
2250
2250
2251
+ static void build_access_struct (EXPLICIT_ACCESS_W * ea , PSID owner ,
2252
+ TRUSTEE_TYPE user_type , mode_t mode_triplet ,
2253
+ ACCESS_MODE allow_deny ) {
2254
+ /*
2255
+ * We map the typical POSIX mode bits r/w/x as the Windows
2256
+ * FILE_GENERIC_{READ,WRITE,EXECUTE} permissions with a little bit of of extra permissions
2257
+ * added on, to deal with directories and win32 idiosyncrasies.
2258
+ */
2259
+ ZeroMemory (ea , sizeof (EXPLICIT_ACCESS_W ));
2260
+
2261
+ /*
2262
+ * Initialize two EXLPICIT_ACCESS structures; one to explicitly allow things, the
2263
+ * other to explicitly deny them. We leave no middle ground for inheritance to mess
2264
+ * things up.
2265
+ */
2266
+ ea -> grfAccessPermissions = 0 ;
2267
+ ea -> grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT ;
2268
+ ea -> Trustee .MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE ;
2269
+ ea -> Trustee .TrusteeForm = TRUSTEE_IS_SID ;
2270
+ ea -> Trustee .TrusteeType = user_type ;
2271
+ ea -> Trustee .ptstrName = owner ;
2272
+
2273
+ ea -> grfAccessMode = allow_deny ;
2274
+
2275
+ /*
2276
+ * We would like to use FILE_GENERIC_* for everything, but unfortunately:
2277
+ *
2278
+ * - This does not include the rights for a directory to delete its children,
2279
+ * so we include that manually with the "write" permission by including the
2280
+ * FILE_ADD_SUBDIRECTORY and FILE_DELETE_CHILD permissions.
2281
+ * - All FILE_GENERIC_* defines share the SYNCHRONIZE permission, which means
2282
+ * that if we deny FILE_GENERIC_WRITE but allow FILE_GENERIC_READ, that one
2283
+ * permission will still be denied. We work around this by only denying the
2284
+ * SYNCHRONIZE permission if read is not allowed, allowing it otherwise.
2285
+ * - We want to be able to set things as read-only even after the ACL has been
2286
+ * set, so we never give up the FILE_WRITE_ATTRIBUTES permission, unless we're
2287
+ * actually being set to 0o000.
2288
+ */
2289
+
2290
+ if (mode_triplet & 0x1 ) {
2291
+ ea -> grfAccessPermissions |= STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE ;
2292
+ if (allow_deny == GRANT_ACCESS ) {
2293
+ ea -> grfAccessPermissions |= SYNCHRONIZE | FILE_WRITE_ATTRIBUTES ;
2294
+ }
2295
+ }
2296
+
2297
+ if (mode_triplet & 0x2 ) {
2298
+ ea -> grfAccessPermissions |= STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD ;
2299
+ if (allow_deny == GRANT_ACCESS ) {
2300
+ ea -> grfAccessPermissions |= SYNCHRONIZE | FILE_WRITE_ATTRIBUTES ;
2301
+ }
2302
+ }
2303
+
2304
+ if (mode_triplet & 0x4 ) {
2305
+ ea -> grfAccessPermissions |= FILE_GENERIC_READ | FILE_WRITE_ATTRIBUTES ;
2306
+ }
2307
+ }
2251
2308
2252
2309
static void fs__chmod (uv_fs_t * req ) {
2253
- int result = _wchmod (req -> file .pathw , req -> fs .info .mode );
2254
- if (result == -1 )
2255
- SET_REQ_WIN32_ERROR (req , _doserrno );
2256
- else
2257
- SET_REQ_RESULT (req , 0 );
2310
+ PACL pOldDACL = NULL , pNewDACL = NULL ;
2311
+ PSID psidOwner = NULL , psidGroup = NULL , psidEveryone = NULL ,
2312
+ psidNull = NULL , psidCreatorGroup = NULL ;
2313
+ PSECURITY_DESCRIPTOR pSD = NULL ;
2314
+ PEXPLICIT_ACCESS_W ea = NULL , pOldEAs = NULL ;
2315
+ SECURITY_INFORMATION si = NULL ;
2316
+ DWORD numGroups = 0 , tokenAccess = 0 , u_mode = 0 , g_mode = 0 , o_mode = 0 ,
2317
+ u_deny_mode = 0 , g_deny_mode = 0 , attr = 0 , new_attr = 0 ;
2318
+ HANDLE hToken = NULL , hImpersonatedToken = NULL ;
2319
+ ULONG numOldEAs = 0 , numNewEAs = 0 , numOtherGroups = 0 ,
2320
+ ea_idx = 0 , ea_write_idx = 0 ;
2321
+
2322
+ /* Create well-known SIDs for various global groups */
2323
+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY ;
2324
+ SID_IDENTIFIER_AUTHORITY SIDAuthNull = SECURITY_NULL_SID_AUTHORITY ;
2325
+ SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY ;
2326
+
2327
+ if (!AllocateAndInitializeSid (& SIDAuthWorld , 1 , SECURITY_WORLD_RID ,
2328
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , & psidEveryone ) ||
2329
+ !AllocateAndInitializeSid (& SIDAuthNull , 1 , SECURITY_NULL_RID ,
2330
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , & psidNull ) ||
2331
+ !AllocateAndInitializeSid (& SIDAuthCreator , 1 , SECURITY_CREATOR_GROUP_RID ,
2332
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , & psidCreatorGroup ) ||
2333
+ !AllocateAndInitializeSid (& SIDAuthWorld , 1 , SECURITY_WORLD_RID ,
2334
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , & psidEveryone )) {
2335
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2336
+ goto chmod_cleanup ;
2337
+ }
2338
+
2339
+ /* Get the old DACL so that we can merge into it */
2340
+ si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
2341
+ DACL_SECURITY_INFORMATION ;
2342
+ if (ERROR_SUCCESS != GetNamedSecurityInfoW (req -> file .pathw , SE_FILE_OBJECT ,
2343
+ si , & psidOwner , & psidGroup ,
2344
+ & pOldDACL , NULL , & pSD )) {
2345
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2346
+ goto chmod_cleanup ;
2347
+ }
2348
+
2349
+ /* Extract EAs from old DACL */
2350
+ if (ERROR_SUCCESS != GetExplicitEntriesFromAclW (pOldDACL , & numOldEAs ,
2351
+ & pOldEAs )) {
2352
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2353
+ goto chmod_cleanup ;
2354
+ }
2355
+
2356
+ /*
2357
+ * Work around Win32 bug where GetExplicitEntriesFromAclW() fails on newly-created files;
2358
+ * We fix it by forcibly clearing some kind of cache by setting the security info with the
2359
+ * old DACL, then attempting to read it in again.
2360
+ */
2361
+ if (numOldEAs != pOldDACL -> AceCount ) {
2362
+ if (ERROR_SUCCESS != SetNamedSecurityInfoW (
2363
+ req -> file .pathw ,
2364
+ SE_FILE_OBJECT ,
2365
+ DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION ,
2366
+ NULL , NULL , pOldDACL , NULL )) {
2367
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2368
+ goto chmod_cleanup ;
2369
+ }
2370
+ if (pSD != NULL ) {
2371
+ LocalFree (pSD );
2372
+ pSD = NULL ;
2373
+ }
2374
+ if (ERROR_SUCCESS != GetNamedSecurityInfoW (req -> file .pathw , SE_FILE_OBJECT ,
2375
+ si , & psidOwner , & psidGroup ,
2376
+ & pOldDACL , NULL , & pSD )) {
2377
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2378
+ goto chmod_cleanup ;
2379
+ }
2380
+ if (pOldEAs != NULL ) {
2381
+ LocalFree (pOldEAs );
2382
+ pOldEAs = NULL ;
2383
+ }
2384
+ if (ERROR_SUCCESS != GetExplicitEntriesFromAclW (pOldDACL , & numOldEAs ,
2385
+ & pOldEAs )) {
2386
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2387
+ goto chmod_cleanup ;
2388
+ }
2389
+ }
2390
+
2391
+ /* If the file does not contain a group owner, we will use the user's 'Creator Group ID' instead */
2392
+ if (EqualSid (psidGroup , psidNull )) {
2393
+ psidGroup = psidCreatorGroup ;
2394
+ }
2395
+
2396
+ /*
2397
+ * We next need to scan all groups that the current user "belongs" to, in order to
2398
+ * set the permissions for those groups to be the same as the "group" bit; so first
2399
+ * we collect a list of group PSIDs:
2400
+ */
2401
+ tokenAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE |
2402
+ STANDARD_RIGHTS_READ ;
2403
+ if (!OpenProcessToken (GetCurrentProcess (), tokenAccess , & hToken )) {
2404
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2405
+ goto chmod_cleanup ;
2406
+ }
2407
+ if (!DuplicateToken (hToken , SecurityImpersonation , & hImpersonatedToken )) {
2408
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2409
+ goto chmod_cleanup ;
2410
+ }
2411
+
2412
+ /* Iterate over all old ACEs, looking for groups that we belong to */
2413
+ for (ea_idx = 0 ; ea_idx < numOldEAs ; ++ ea_idx ) {
2414
+ BOOL isMember = FALSE;
2415
+ PSID pEASid = (PSID )pOldEAs [ea_idx ].Trustee .ptstrName ;
2416
+ /* Skip this EA if it isn't an SID, or it is "Everyone" or our actual group */
2417
+ if (pOldEAs [ea_idx ].Trustee .TrusteeForm != TRUSTEE_IS_SID ||
2418
+ EqualSid (pEASid , psidEveryone ) ||
2419
+ EqualSid (pEASid , psidGroup )) {
2420
+ continue ;
2421
+ }
2422
+
2423
+ /* Check to see if our user is a member of this group */
2424
+ if (!CheckTokenMembership (hImpersonatedToken , pEASid , & isMember )) {
2425
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2426
+ goto chmod_cleanup ;
2427
+ }
2428
+
2429
+ /* If we're a member, then count it */
2430
+ if (isMember ) {
2431
+ numOtherGroups ++ ;
2432
+ }
2433
+ }
2434
+
2435
+ /* Create an ACE for each triplet (user, group, other) */
2436
+ numNewEAs = 8 + 3 * numOtherGroups ;
2437
+ ea = (PEXPLICIT_ACCESS_W ) malloc (sizeof (EXPLICIT_ACCESS_W )* numNewEAs );
2438
+ u_mode = ((req -> fs .info .mode & S_IRWXU ) >> 6 );
2439
+ g_mode = ((req -> fs .info .mode & S_IRWXG ) >> 3 );
2440
+ o_mode = ((req -> fs .info .mode & S_IRWXO ) >> 0 );
2441
+
2442
+ /* We start by revoking previous permissions for trustees we care about */
2443
+ build_access_struct (& ea [0 ], psidOwner , TRUSTEE_IS_USER , 0 , REVOKE_ACCESS );
2444
+ build_access_struct (& ea [1 ], psidGroup , TRUSTEE_IS_GROUP , 0 , REVOKE_ACCESS );
2445
+ build_access_struct (& ea [2 ], psidEveryone , TRUSTEE_IS_GROUP , 0 , REVOKE_ACCESS );
2446
+
2447
+ /*
2448
+ * We also add explicit denies to user and group if the user shouldn't have
2449
+ * a permission but the group or everyone can, for instance.
2450
+ */
2451
+ u_deny_mode = (~u_mode ) & (g_mode | o_mode );
2452
+ g_deny_mode = (~g_mode ) & o_mode ;
2453
+ build_access_struct (& ea [3 ], psidOwner , TRUSTEE_IS_USER , u_deny_mode , DENY_ACCESS );
2454
+ build_access_struct (& ea [4 ], psidGroup , TRUSTEE_IS_GROUP , g_deny_mode , DENY_ACCESS );
2455
+
2456
+ /* Next, add explicit allows for (owner, group, other) */
2457
+ build_access_struct (& ea [5 ], psidOwner , TRUSTEE_IS_USER , u_mode , SET_ACCESS );
2458
+ build_access_struct (& ea [6 ], psidGroup , TRUSTEE_IS_GROUP , g_mode , SET_ACCESS );
2459
+ build_access_struct (& ea [7 ], psidEveryone , TRUSTEE_IS_GROUP , o_mode , SET_ACCESS );
2460
+
2461
+ /*
2462
+ * Iterate over all old ACEs, looking for groups that we belong to, and setting
2463
+ * the appropriate access bits for those groups (as g_mode)
2464
+ */
2465
+ ea_write_idx = 8 ;
2466
+ for (ea_idx = 0 ; ea_idx < numOldEAs ; ++ ea_idx ) {
2467
+ BOOL isMember = FALSE;
2468
+ PSID pEASid = (PSID )pOldEAs [ea_idx ].Trustee .ptstrName ;
2469
+ /* Skip this EA if it isn't an SID, or it is "Everyone" or our actual group */
2470
+ if (pOldEAs [ea_idx ].Trustee .TrusteeForm != TRUSTEE_IS_SID ||
2471
+ EqualSid (pEASid , psidEveryone ) ||
2472
+ EqualSid (pEASid , psidGroup )) {
2473
+ continue ;
2474
+ }
2475
+
2476
+ /* Check to see if our user is a member of this group */
2477
+ if (!CheckTokenMembership (hImpersonatedToken , pEASid , & isMember )) {
2478
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2479
+ goto chmod_cleanup ;
2480
+ }
2481
+
2482
+ /*
2483
+ * If we're a member, then count it. We limit our `ea_write_idx` to avoid
2484
+ * the unlikely event that we have been added to a group since we first
2485
+ * calculated `numOtherGroups`.
2486
+ */
2487
+ if (isMember && ea_write_idx < numNewEAs ) {
2488
+ build_access_struct (& ea [ea_write_idx ], pEASid , TRUSTEE_IS_GROUP , 0 , REVOKE_ACCESS );
2489
+ build_access_struct (& ea [ea_write_idx + 1 ], pEASid , TRUSTEE_IS_GROUP , g_deny_mode , DENY_ACCESS );
2490
+ build_access_struct (& ea [ea_write_idx + 2 ], pEASid , TRUSTEE_IS_GROUP , g_mode , SET_ACCESS );
2491
+ ea_write_idx += 3 ;
2492
+ }
2493
+ }
2494
+
2495
+ /* Set entries in the ACL object */
2496
+ if (ERROR_SUCCESS != SetEntriesInAclW (numNewEAs , & ea [0 ], pOldDACL , & pNewDACL )) {
2497
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2498
+ goto chmod_cleanup ;
2499
+ }
2500
+
2501
+ /* If none of the write bits are set, we want to mark the file as read-only.
2502
+ * Alternatively, if it was marked as read-only, unmark it if we have at least
2503
+ * one writable group set. */
2504
+ attr = GetFileAttributesW (req -> file .pathw );
2505
+ if (attr == INVALID_FILE_ATTRIBUTES ) {
2506
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2507
+ goto chmod_cleanup ;
2508
+ }
2509
+ new_attr = attr ;
2510
+ if ((req -> fs .info .mode & (S_IWUSR | S_IWGRP | S_IWOTH )) == 0 ) {
2511
+ new_attr |= FILE_ATTRIBUTE_READONLY ;
2512
+ }
2513
+ if ((req -> fs .info .mode & (S_IWUSR | S_IWGRP | S_IWOTH )) != 0 ) {
2514
+ new_attr &= ~FILE_ATTRIBUTE_READONLY ;
2515
+ }
2516
+
2517
+ /*
2518
+ * Now we actually do the setting. We only call SetFileAttributes() if the
2519
+ * attributes have actually changed.
2520
+ */
2521
+ if (new_attr != attr ) {
2522
+ if (!SetFileAttributesW (req -> file .pathw , new_attr )) {
2523
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2524
+ goto chmod_cleanup ;
2525
+ }
2526
+ }
2527
+ if (ERROR_SUCCESS != SetNamedSecurityInfoW (
2528
+ req -> file .pathw ,
2529
+ SE_FILE_OBJECT ,
2530
+ DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION ,
2531
+ NULL , NULL , pNewDACL , NULL )) {
2532
+ SET_REQ_WIN32_ERROR (req , GetLastError ());
2533
+ goto chmod_cleanup ;
2534
+ }
2535
+
2536
+ SET_REQ_SUCCESS (req );
2537
+
2538
+ chmod_cleanup :
2539
+ if (pSD != NULL ) {
2540
+ LocalFree (pSD );
2541
+ }
2542
+ if (pNewDACL != NULL ) {
2543
+ LocalFree (pNewDACL );
2544
+ }
2545
+ if (psidEveryone != NULL ) {
2546
+ FreeSid (psidEveryone );
2547
+ }
2548
+ if (psidNull != NULL ) {
2549
+ FreeSid (psidNull );
2550
+ }
2551
+ if (psidCreatorGroup != NULL ) {
2552
+ FreeSid (psidCreatorGroup );
2553
+ }
2554
+ if (pOldEAs != NULL ) {
2555
+ LocalFree (pOldEAs );
2556
+ }
2557
+ if (ea != NULL ) {
2558
+ free (ea );
2559
+ }
2258
2560
}
2259
2561
2260
2562
0 commit comments