|
35 | 35 | #include "handle-inl.h"
|
36 | 36 | #include "fs-fd-hash-inl.h"
|
37 | 37 |
|
| 38 | +#include <wincrypt.h> |
| 39 | +#include <accctrl.h> /* for SE_FILE_OBJECT constant */ |
| 40 | +#include <aclapi.h> /* for SetEntriesInAcl() */ |
| 41 | + |
38 | 42 |
|
39 | 43 | #define UV_FS_FREE_PATHS 0x0002
|
40 | 44 | #define UV_FS_FREE_PTR 0x0008
|
@@ -1049,7 +1053,7 @@ void fs__write_filemap(uv_fs_t* req, HANDLE file,
|
1049 | 1053 | }
|
1050 | 1054 |
|
1051 | 1055 | void fs__write(uv_fs_t* req) {
|
1052 |
| - HANDLE handle = req->file.hFile;; |
| 1056 | + HANDLE handle = req->file.hFile; |
1053 | 1057 | int64_t offset = req->fs.info.offset;
|
1054 | 1058 | OVERLAPPED overlapped, *overlapped_ptr;
|
1055 | 1059 | LARGE_INTEGER offset_;
|
@@ -2113,20 +2117,135 @@ static void fs__access(uv_fs_t* req) {
|
2113 | 2117 | }
|
2114 | 2118 |
|
2115 | 2119 | /*
|
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) |
2121 | 2124 | */
|
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))) { |
2127 | 2128 | SET_REQ_WIN32_ERROR(req, UV_EPERM);
|
| 2129 | + return; |
2128 | 2130 | }
|
2129 | 2131 |
|
| 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); |
2130 | 2249 | }
|
2131 | 2250 |
|
2132 | 2251 |
|
|
0 commit comments