From 1dcf32479840be96210d0807037351626cb82e2f Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Thu, 7 May 2020 23:26:41 -0700 Subject: [PATCH] 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. --- src/win/fs.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 11 deletions(-) diff --git a/src/win/fs.c b/src/win/fs.c index e14e66987d9..2769b43a4b0 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -35,6 +35,10 @@ #include "handle-inl.h" #include "fs-fd-hash-inl.h" +#include +#include /* for SE_FILE_OBJECT constant */ +#include /* for SetEntriesInAcl() */ + #define UV_FS_FREE_PATHS 0x0002 #define UV_FS_FREE_PTR 0x0008 @@ -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_; @@ -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); }