Skip to content

Commit 1dcf324

Browse files
committed
win,fs: Upgrade fs__access() to respond to X_OK.
By utilizing the `AccessCheck()` ACL API, we can interrogate the filesystem for executable permissions on a particular file for the given process/user. As this operation is slightly expensive, it is only done if the user requests it by setting the `mode` parameter with `X_OK`. In particular, this implementation checks for the `FILE_GENERIC_EXECUTE` permission within the ACL entries of the given file.
1 parent 559d5d0 commit 1dcf324

File tree

1 file changed

+130
-11
lines changed

1 file changed

+130
-11
lines changed

src/win/fs.c

+130-11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
#include "handle-inl.h"
3636
#include "fs-fd-hash-inl.h"
3737

38+
#include <wincrypt.h>
39+
#include <accctrl.h> /* for SE_FILE_OBJECT constant */
40+
#include <aclapi.h> /* for SetEntriesInAcl() */
41+
3842

3943
#define UV_FS_FREE_PATHS 0x0002
4044
#define UV_FS_FREE_PTR 0x0008
@@ -1049,7 +1053,7 @@ void fs__write_filemap(uv_fs_t* req, HANDLE file,
10491053
}
10501054

10511055
void fs__write(uv_fs_t* req) {
1052-
HANDLE handle = req->file.hFile;;
1056+
HANDLE handle = req->file.hFile;
10531057
int64_t offset = req->fs.info.offset;
10541058
OVERLAPPED overlapped, *overlapped_ptr;
10551059
LARGE_INTEGER offset_;
@@ -2113,20 +2117,135 @@ static void fs__access(uv_fs_t* req) {
21132117
}
21142118

21152119
/*
2116-
* Access is possible if
2117-
* - write access wasn't requested,
2118-
* - or the file isn't read-only,
2119-
* - or it's a directory.
2120-
* (Directories cannot be read-only on Windows.)
2120+
* If write access was requested, ensure that either
2121+
* the requested file is not marked as READONLY,
2122+
* or that it's actually a directory (directories
2123+
* cannot be read-only in Windows)
21212124
*/
2122-
if (!(req->fs.info.mode & W_OK) ||
2123-
!(attr & FILE_ATTRIBUTE_READONLY) ||
2124-
(attr & FILE_ATTRIBUTE_DIRECTORY)) {
2125-
SET_REQ_RESULT(req, 0);
2126-
} else {
2125+
if ((req->fs.info.mode & W_OK) &&
2126+
((attr & FILE_ATTRIBUTE_READONLY) ||
2127+
!(attr & FILE_ATTRIBUTE_DIRECTORY))) {
21272128
SET_REQ_WIN32_ERROR(req, UV_EPERM);
2129+
return;
21282130
}
21292131

2132+
/*
2133+
* If executable access was requested, we must check
2134+
* with the AccessCheck() ACL call. This is mildly
2135+
* expensive, so only do it if `X_OK` was requested.
2136+
*/
2137+
if (req->fs.info.mode & X_OK) {
2138+
DWORD sdLen = 0, err = 0, tokenAccess = 0, executeAccessRights = 0,
2139+
grantedAccess = 0, privilegesLen = 0;
2140+
SECURITY_INFORMATION si = NULL;
2141+
PSECURITY_DESCRIPTOR sd = NULL;
2142+
HANDLE hToken = NULL, hImpersonatedToken = NULL;
2143+
GENERIC_MAPPING mapping = { 0xFFFFFFFF };
2144+
PRIVILEGE_SET privileges = { 0 };
2145+
BOOL result = FALSE;
2146+
2147+
/*
2148+
* First, we must allocate enough space. We do that
2149+
* by first passing in a zero-length null pointer,
2150+
* storing the desired length into `sd_length`.
2151+
* We expect this call to fail with a certain error code.
2152+
*/
2153+
si = OWNER_SECURITY_INFORMATION |
2154+
GROUP_SECURITY_INFORMATION |
2155+
DACL_SECURITY_INFORMATION;
2156+
if (GetFileSecurityW(req->file.pathw, si, NULL, 0, &sdLen)) {
2157+
SET_REQ_RESULT(req, UV_UNKNOWN);
2158+
return;
2159+
}
2160+
err = GetLastError();
2161+
if (ERROR_INSUFFICIENT_BUFFER != err) {
2162+
SET_REQ_WIN32_ERROR(req, err);
2163+
return;
2164+
}
2165+
2166+
/* Now that we know how big `sd` must be, allocate it */
2167+
sd = (PSECURITY_DESCRIPTOR)uv__malloc(sdLen);
2168+
if (!sd) {
2169+
uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
2170+
}
2171+
2172+
/* Call `GetFileSecurity()` with the requisite `sd` structure. */
2173+
if (!GetFileSecurityW(req->file.pathw, si, sd, sdLen, &sdLen)) {
2174+
SET_REQ_WIN32_ERROR(req, GetLastError());
2175+
goto accesscheck_cleanup;
2176+
}
2177+
2178+
/*
2179+
* Next we need to create an impersonation token representing
2180+
* the current user and the current process.
2181+
*/
2182+
tokenAccess = TOKEN_IMPERSONATE |
2183+
TOKEN_QUERY |
2184+
TOKEN_DUPLICATE |
2185+
STANDARD_RIGHTS_READ;
2186+
if (!OpenProcessToken(GetCurrentProcess(), tokenAccess, &hToken )) {
2187+
SET_REQ_WIN32_ERROR(req, GetLastError());
2188+
goto accesscheck_cleanup;
2189+
}
2190+
if (!DuplicateToken(hToken, SecurityImpersonation, &hImpersonatedToken)) {
2191+
SET_REQ_WIN32_ERROR(req, GetLastError());
2192+
goto accesscheck_cleanup;
2193+
}
2194+
2195+
/*
2196+
* Next, construct a mapping from generic access rights to
2197+
* the more specific access rights that AccessCheck expects.
2198+
*/
2199+
executeAccessRights = FILE_GENERIC_EXECUTE;
2200+
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
2201+
MapGenericMask(&executeAccessRights, &mapping);
2202+
2203+
privilegesLen = sizeof(privileges);
2204+
result = FALSE;
2205+
if (AccessCheck(sd,
2206+
hImpersonatedToken,
2207+
executeAccessRights,
2208+
&mapping,
2209+
&privileges,
2210+
&privilegesLen,
2211+
&grantedAccess,
2212+
&result)) {
2213+
/*
2214+
* If AccessCheck passes, nothing went wrong, but
2215+
* we must still check that we have access.
2216+
*/
2217+
if (!result) {
2218+
SET_REQ_WIN32_ERROR(req, UV_EPERM);
2219+
goto accesscheck_cleanup;
2220+
}
2221+
} else {
2222+
/*
2223+
* This signifies that something went wrong with the
2224+
* actual AccessCheck() invocation itself.
2225+
*/
2226+
SET_REQ_WIN32_ERROR(req, GetLastError());
2227+
goto accesscheck_cleanup;
2228+
}
2229+
2230+
accesscheck_cleanup:
2231+
uv__free(sd);
2232+
if (hImpersonatedToken != NULL) {
2233+
CloseHandle(hImpersonatedToken);
2234+
}
2235+
if (hToken != NULL) {
2236+
CloseHandle(hToken);
2237+
}
2238+
/*
2239+
* If the result is false, return immediately.
2240+
* Some error code has been set in the `req` already.
2241+
*/
2242+
if (!result) {
2243+
return;
2244+
}
2245+
}
2246+
2247+
/* If we get to the end, everything worked out. */
2248+
SET_REQ_SUCCESS(req);
21302249
}
21312250

21322251

0 commit comments

Comments
 (0)