@@ -2248,13 +2248,315 @@ static void fs__access(uv_fs_t* req) {
22482248 SET_REQ_SUCCESS (req );
22492249}
22502250
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+ }
22512308
22522309static 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+ }
22582560}
22592561
22602562
0 commit comments