Improve ioutil access impl and refactor tsrm_win32_access

This commit is contained in:
Anatol Belski 2017-12-08 16:35:45 +01:00
parent 2fbdaec03c
commit a9a49b8250
3 changed files with 167 additions and 161 deletions

View file

@ -209,190 +209,175 @@ TSRM_API int tsrm_win32_access(const char *pathname, int mode)
realpath_cache_bucket * bucket = NULL; realpath_cache_bucket * bucket = NULL;
char * real_path = NULL; char * real_path = NULL;
if(!IS_ABSOLUTE_PATH(pathname, strlen(pathname)+1)) {
real_path = (char *)malloc(MAXPATHLEN);
if(tsrm_realpath(pathname, real_path) == NULL) {
SET_ERRNO_FROM_WIN32_CODE(ERROR_FILE_NOT_FOUND);
return -1;
}
pathname = real_path;
}
PHP_WIN32_IOUTIL_INIT_W(pathname) PHP_WIN32_IOUTIL_INIT_W(pathname)
if (!pathw) { if (!pathw) {
free(real_path);
return -1; return -1;
} }
if (mode == 1 /*X_OK*/) { /* Either access call failed, or the mode was asking for a specific check.*/
DWORD type; int ret = php_win32_ioutil_access_w(pathw, mode);
int ret; if (0 > ret || X_OK == mode || F_OK == mode) {
ret = GetBinaryTypeW(pathw, &type) ? 0 : -1;
PHP_WIN32_IOUTIL_CLEANUP_W() PHP_WIN32_IOUTIL_CLEANUP_W()
free(real_path);
return ret; return ret;
} else { }
if(!IS_ABSOLUTE_PATH(pathname, strlen(pathname)+1)) {
real_path = (char *)malloc(MAXPATHLEN);
if(tsrm_realpath(pathname, real_path) == NULL) {
goto Finished;
}
pathname = real_path;
PHP_WIN32_IOUTIL_REINIT_W(pathname);
}
if(php_win32_ioutil_access(pathname, mode)) {
PHP_WIN32_IOUTIL_CLEANUP_W()
free(real_path);
return errno;
}
/* If only existence check is made, return now */
if (mode == 0) {
PHP_WIN32_IOUTIL_CLEANUP_W()
free(real_path);
return 0;
}
/* Only in NTS when impersonate==1 (aka FastCGI) */ /* Only in NTS when impersonate==1 (aka FastCGI) */
/* /*
AccessCheck() requires an impersonation token. We first get a primary AccessCheck() requires an impersonation token. We first get a primary
token and then create a duplicate impersonation token. The token and then create a duplicate impersonation token. The
impersonation token is not actually assigned to the thread, but is impersonation token is not actually assigned to the thread, but is
used in the call to AccessCheck. Thus, this function itself never used in the call to AccessCheck. Thus, this function itself never
impersonates, but does use the identity of the thread. If the thread impersonates, but does use the identity of the thread. If the thread
was impersonating already, this function uses that impersonation context. was impersonating already, this function uses that impersonation context.
*/ */
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) { if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &thread_token)) {
if (GetLastError() == ERROR_NO_TOKEN) { if (GetLastError() == ERROR_NO_TOKEN) {
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &thread_token)) { if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &thread_token)) {
TWG(impersonation_token) = NULL; TWG(impersonation_token) = NULL;
goto Finished; goto Finished;
} }
}
} }
}
/* token_sid will be freed in tsrmwin32_dtor */ /* token_sid will be freed in tsrmwin32_dtor */
token_sid = tsrm_win32_get_token_sid(thread_token); token_sid = tsrm_win32_get_token_sid(thread_token);
if (!token_sid) { if (!token_sid) {
if (TWG(impersonation_token_sid)) { if (TWG(impersonation_token_sid)) {
free(TWG(impersonation_token_sid)); free(TWG(impersonation_token_sid));
} }
TWG(impersonation_token_sid) = NULL; TWG(impersonation_token_sid) = NULL;
goto Finished;
}
/* Different identity, we need a new impersontated token as well */
if (!TWG(impersonation_token_sid) || !EqualSid(token_sid, TWG(impersonation_token_sid))) {
if (TWG(impersonation_token_sid)) {
free(TWG(impersonation_token_sid));
}
TWG(impersonation_token_sid) = token_sid;
/* Duplicate the token as impersonated token */
if (!DuplicateToken(thread_token, SecurityImpersonation, &TWG(impersonation_token))) {
goto Finished; goto Finished;
} }
} else {
/* we already have it, free it then */
free(token_sid);
}
/* Different identity, we need a new impersontated token as well */ if (CWDG(realpath_cache_size_limit)) {
if (!TWG(impersonation_token_sid) || !EqualSid(token_sid, TWG(impersonation_token_sid))) { t = time(0);
if (TWG(impersonation_token_sid)) { bucket = realpath_cache_lookup(pathname, strlen(pathname), t);
free(TWG(impersonation_token_sid)); if(bucket == NULL && real_path == NULL) {
/* We used the pathname directly. Call tsrm_realpath */
/* so that entry is created in realpath cache */
real_path = (char *)malloc(MAXPATHLEN);
if(tsrm_realpath(pathname, real_path) != NULL) {
pathname = real_path;
bucket = realpath_cache_lookup(pathname, strlen(pathname), t);
PHP_WIN32_IOUTIL_REINIT_W(pathname);
} }
TWG(impersonation_token_sid) = token_sid;
/* Duplicate the token as impersonated token */
if (!DuplicateToken(thread_token, SecurityImpersonation, &TWG(impersonation_token))) {
goto Finished;
}
} else {
/* we already have it, free it then */
free(token_sid);
} }
}
if (CWDG(realpath_cache_size_limit)) { /* Do a full access check because access() will only check read-only attribute */
t = time(0); if(mode == 0 || mode > 6) {
bucket = realpath_cache_lookup(pathname, strlen(pathname), t); if(bucket != NULL && bucket->is_rvalid) {
if(bucket == NULL && real_path == NULL) { fAccess = bucket->is_readable;
/* We used the pathname directly. Call tsrm_realpath */
/* so that entry is created in realpath cache */
real_path = (char *)malloc(MAXPATHLEN);
if(tsrm_realpath(pathname, real_path) != NULL) {
pathname = real_path;
bucket = realpath_cache_lookup(pathname, strlen(pathname), t);
PHP_WIN32_IOUTIL_REINIT_W(pathname);
}
}
}
/* Do a full access check because access() will only check read-only attribute */
if(mode == 0 || mode > 6) {
if(bucket != NULL && bucket->is_rvalid) {
fAccess = bucket->is_readable;
goto Finished;
}
desired_access = FILE_GENERIC_READ;
} else if(mode <= 2) {
if(bucket != NULL && bucket->is_wvalid) {
fAccess = bucket->is_writable;
goto Finished;
}
desired_access = FILE_GENERIC_WRITE;
} else if(mode <= 4) {
if(bucket != NULL && bucket->is_rvalid) {
fAccess = bucket->is_readable;
goto Finished;
}
desired_access = FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS;
} else { // if(mode <= 6)
if(bucket != NULL && bucket->is_rvalid && bucket->is_wvalid) {
fAccess = bucket->is_readable & bucket->is_writable;
goto Finished;
}
desired_access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
}
if(TWG(impersonation_token) == NULL) {
goto Finished; goto Finished;
} }
desired_access = FILE_GENERIC_READ;
/* Get size of security buffer. Call is expected to fail */ } else if(mode <= 2) {
if(GetFileSecurityW(pathw, sec_info, NULL, 0, &sec_desc_length)) { if(bucket != NULL && bucket->is_wvalid) {
fAccess = bucket->is_writable;
goto Finished; goto Finished;
} }
desired_access = FILE_GENERIC_WRITE;
psec_desc = (BYTE *)malloc(sec_desc_length); } else if(mode <= 4) {
if(psec_desc == NULL || if(bucket != NULL && bucket->is_rvalid) {
!GetFileSecurityW(pathw, sec_info, (PSECURITY_DESCRIPTOR)psec_desc, sec_desc_length, &sec_desc_length)) { fAccess = bucket->is_readable;
goto Finished; goto Finished;
} }
desired_access = FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS;
MapGenericMask(&desired_access, &gen_map); } else { // if(mode <= 6)
if(bucket != NULL && bucket->is_rvalid && bucket->is_wvalid) {
if(!AccessCheck((PSECURITY_DESCRIPTOR)psec_desc, TWG(impersonation_token), desired_access, &gen_map, &privilege_set, &priv_set_length, &granted_access, &fAccess)) { fAccess = bucket->is_readable & bucket->is_writable;
goto Finished_Impersonate; goto Finished;
} }
desired_access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
}
/* Keep the result in realpath_cache */ if(TWG(impersonation_token) == NULL) {
if(bucket != NULL) { goto Finished;
if(desired_access == (FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS)) { }
bucket->is_rvalid = 1;
bucket->is_readable = fAccess; /* Get size of security buffer. Call is expected to fail */
} if(GetFileSecurityW(pathw, sec_info, NULL, 0, &sec_desc_length)) {
else if(desired_access == FILE_GENERIC_WRITE) { goto Finished;
bucket->is_wvalid = 1; }
bucket->is_writable = fAccess;
} else if (desired_access == (FILE_GENERIC_READ | FILE_GENERIC_WRITE)) { psec_desc = (BYTE *)malloc(sec_desc_length);
bucket->is_rvalid = 1; if(psec_desc == NULL ||
bucket->is_readable = fAccess; !GetFileSecurityW(pathw, sec_info, (PSECURITY_DESCRIPTOR)psec_desc, sec_desc_length, &sec_desc_length)) {
bucket->is_wvalid = 1; goto Finished;
bucket->is_writable = fAccess; }
}
MapGenericMask(&desired_access, &gen_map);
if(!AccessCheck((PSECURITY_DESCRIPTOR)psec_desc, TWG(impersonation_token), desired_access, &gen_map, &privilege_set, &priv_set_length, &granted_access, &fAccess)) {
goto Finished_Impersonate;
}
/* Keep the result in realpath_cache */
if(bucket != NULL) {
if(desired_access == (FILE_GENERIC_READ|FILE_FLAG_BACKUP_SEMANTICS)) {
bucket->is_rvalid = 1;
bucket->is_readable = fAccess;
} }
else if(desired_access == FILE_GENERIC_WRITE) {
bucket->is_wvalid = 1;
bucket->is_writable = fAccess;
} else if (desired_access == (FILE_GENERIC_READ | FILE_GENERIC_WRITE)) {
bucket->is_rvalid = 1;
bucket->is_readable = fAccess;
bucket->is_wvalid = 1;
bucket->is_writable = fAccess;
}
}
Finished_Impersonate: Finished_Impersonate:
if(psec_desc != NULL) { if(psec_desc != NULL) {
free(psec_desc); free(psec_desc);
psec_desc = NULL; psec_desc = NULL;
} }
Finished: Finished:
if(thread_token != NULL) { if(thread_token != NULL) {
CloseHandle(thread_token); CloseHandle(thread_token);
} }
if(real_path != NULL) { if(real_path != NULL) {
free(real_path); free(real_path);
real_path = NULL; real_path = NULL;
} }
PHP_WIN32_IOUTIL_CLEANUP_W() PHP_WIN32_IOUTIL_CLEANUP_W()
if(fAccess == FALSE) { if(fAccess == FALSE) {
errno = EACCES; errno = EACCES;
return errno; return errno;
} else { } else {
return 0; return 0;
}
} }
}/*}}}*/ }/*}}}*/

View file

@ -655,13 +655,33 @@ BOOL php_win32_ioutil_init(void)
return TRUE; return TRUE;
}/*}}}*/ }/*}}}*/
/* an extended version could be implemented, for now direct functions can be used. */
#if 0
PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode) PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode)
{ {/*{{{*/
return _waccess(path, mode); DWORD attr, err;
}
#endif if ((mode & X_OK) == X_OK) {
DWORD type;
return GetBinaryTypeW(path, &type) ? 0 : -1;
}
attr = GetFileAttributesW(path);
if (attr == INVALID_FILE_ATTRIBUTES) {
err = GetLastError();
SET_ERRNO_FROM_WIN32_CODE(err);
return -1;
}
if (F_OK == mode) {
return 0;
}
if ((mode &W_OK) == W_OK && (attr & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
SET_ERRNO_FROM_WIN32_CODE(ERROR_ACCESS_DENIED);
return -1;
}
return 0;
}/*}}}*/
#if 0 #if 0
PW32IO HANDLE php_win32_ioutil_findfirstfile_w(char *path, WIN32_FIND_DATA *data) PW32IO HANDLE php_win32_ioutil_findfirstfile_w(char *path, WIN32_FIND_DATA *data)

View file

@ -242,6 +242,7 @@ PW32IO int php_win32_ioutil_chdir_w(const wchar_t *path);
PW32IO int php_win32_ioutil_rename_w(const wchar_t *oldname, const wchar_t *newname); PW32IO int php_win32_ioutil_rename_w(const wchar_t *oldname, const wchar_t *newname);
PW32IO wchar_t *php_win32_ioutil_getcwd_w(wchar_t *buf, size_t len); PW32IO wchar_t *php_win32_ioutil_getcwd_w(wchar_t *buf, size_t len);
PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path); PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path);
PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode);
#if 0 #if 0
PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode); PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode);
@ -264,14 +265,14 @@ __forceinline static int php_win32_ioutil_access(const char *path, mode_t mode)
PHP_WIN32_IOUTIL_CHECK_PATH_W(pathw, -1, 1) PHP_WIN32_IOUTIL_CHECK_PATH_W(pathw, -1, 1)
ret = _waccess(pathw, mode); ret = php_win32_ioutil_access_w(pathw, mode);
if (0 > ret) { if (0 > ret) {
_get_errno(&err); err = GetLastError();
} }
PHP_WIN32_IOUTIL_CLEANUP_W() PHP_WIN32_IOUTIL_CLEANUP_W()
if (0 > ret) { if (0 > ret) {
_set_errno(err); SET_ERRNO_FROM_WIN32_CODE(err);
} }
return ret; return ret;