Skip to content

Commit

Permalink
win,fs: Upgrade fs__access() to respond to X_OK.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
staticfloat committed Sep 22, 2020
1 parent 559d5d0 commit 1dcf324
Showing 1 changed file with 130 additions and 11 deletions.
141 changes: 130 additions & 11 deletions src/win/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#include "handle-inl.h"
#include "fs-fd-hash-inl.h"

#include <wincrypt.h>
#include <accctrl.h> /* for SE_FILE_OBJECT constant */
#include <aclapi.h> /* for SetEntriesInAcl() */


#define UV_FS_FREE_PATHS 0x0002
#define UV_FS_FREE_PTR 0x0008
Expand Down Expand Up @@ -1049,7 +1053,7 @@ void fs__write_filemap(uv_fs_t* req, HANDLE file,
}

void fs__write(uv_fs_t* req) {
HANDLE handle = req->file.hFile;;
HANDLE handle = req->file.hFile;
int64_t offset = req->fs.info.offset;
OVERLAPPED overlapped, *overlapped_ptr;
LARGE_INTEGER offset_;
Expand Down Expand Up @@ -2113,20 +2117,135 @@ static void fs__access(uv_fs_t* req) {
}

/*
* Access is possible if
* - write access wasn't requested,
* - or the file isn't read-only,
* - or it's a directory.
* (Directories cannot be read-only on Windows.)
* If write access was requested, ensure that either
* the requested file is not marked as READONLY,
* or that it's actually a directory (directories
* cannot be read-only in Windows)
*/
if (!(req->fs.info.mode & W_OK) ||
!(attr & FILE_ATTRIBUTE_READONLY) ||
(attr & FILE_ATTRIBUTE_DIRECTORY)) {
SET_REQ_RESULT(req, 0);
} else {
if ((req->fs.info.mode & W_OK) &&
((attr & FILE_ATTRIBUTE_READONLY) ||
!(attr & FILE_ATTRIBUTE_DIRECTORY))) {
SET_REQ_WIN32_ERROR(req, UV_EPERM);
return;
}

/*
* If executable access was requested, we must check
* with the AccessCheck() ACL call. This is mildly
* expensive, so only do it if `X_OK` was requested.
*/
if (req->fs.info.mode & X_OK) {
DWORD sdLen = 0, err = 0, tokenAccess = 0, executeAccessRights = 0,
grantedAccess = 0, privilegesLen = 0;
SECURITY_INFORMATION si = NULL;
PSECURITY_DESCRIPTOR sd = NULL;
HANDLE hToken = NULL, hImpersonatedToken = NULL;
GENERIC_MAPPING mapping = { 0xFFFFFFFF };
PRIVILEGE_SET privileges = { 0 };
BOOL result = FALSE;

/*
* First, we must allocate enough space. We do that
* by first passing in a zero-length null pointer,
* storing the desired length into `sd_length`.
* We expect this call to fail with a certain error code.
*/
si = OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION;
if (GetFileSecurityW(req->file.pathw, si, NULL, 0, &sdLen)) {
SET_REQ_RESULT(req, UV_UNKNOWN);
return;
}
err = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER != err) {
SET_REQ_WIN32_ERROR(req, err);
return;
}

/* Now that we know how big `sd` must be, allocate it */
sd = (PSECURITY_DESCRIPTOR)uv__malloc(sdLen);
if (!sd) {
uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
}

/* Call `GetFileSecurity()` with the requisite `sd` structure. */
if (!GetFileSecurityW(req->file.pathw, si, sd, sdLen, &sdLen)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto accesscheck_cleanup;
}

/*
* Next we need to create an impersonation token representing
* the current user and the current process.
*/
tokenAccess = TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_DUPLICATE |
STANDARD_RIGHTS_READ;
if (!OpenProcessToken(GetCurrentProcess(), tokenAccess, &hToken )) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto accesscheck_cleanup;
}
if (!DuplicateToken(hToken, SecurityImpersonation, &hImpersonatedToken)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto accesscheck_cleanup;
}

/*
* Next, construct a mapping from generic access rights to
* the more specific access rights that AccessCheck expects.
*/
executeAccessRights = FILE_GENERIC_EXECUTE;
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
MapGenericMask(&executeAccessRights, &mapping);

privilegesLen = sizeof(privileges);
result = FALSE;
if (AccessCheck(sd,
hImpersonatedToken,
executeAccessRights,
&mapping,
&privileges,
&privilegesLen,
&grantedAccess,
&result)) {
/*
* If AccessCheck passes, nothing went wrong, but
* we must still check that we have access.
*/
if (!result) {
SET_REQ_WIN32_ERROR(req, UV_EPERM);
goto accesscheck_cleanup;
}
} else {
/*
* This signifies that something went wrong with the
* actual AccessCheck() invocation itself.
*/
SET_REQ_WIN32_ERROR(req, GetLastError());
goto accesscheck_cleanup;
}

accesscheck_cleanup:
uv__free(sd);
if (hImpersonatedToken != NULL) {
CloseHandle(hImpersonatedToken);
}
if (hToken != NULL) {
CloseHandle(hToken);
}
/*
* If the result is false, return immediately.
* Some error code has been set in the `req` already.
*/
if (!result) {
return;
}
}

/* If we get to the end, everything worked out. */
SET_REQ_SUCCESS(req);
}


Expand Down

0 comments on commit 1dcf324

Please sign in to comment.