From 74a1323b057826377fb454d1816c944562a6dc96 Mon Sep 17 00:00:00 2001 From: Pierre Joye Date: Fri, 26 Jun 2009 07:44:57 +0000 Subject: [PATCH] - MF53: - Windows ACL cache support, update existing tests and add a new one - #48535, file_exists returns false when impersonate is used --- TSRM/tsrm_virtual_cwd.c | 156 +++++++++++++++++++++++++++++++++++++--- TSRM/tsrm_virtual_cwd.h | 7 ++ TSRM/tsrm_win32.c | 115 +++++++++++++++++++++-------- 3 files changed, 241 insertions(+), 37 deletions(-) diff --git a/TSRM/tsrm_virtual_cwd.c b/TSRM/tsrm_virtual_cwd.c index bcc6029f20..72307f144b 100644 --- a/TSRM/tsrm_virtual_cwd.c +++ b/TSRM/tsrm_virtual_cwd.c @@ -35,6 +35,9 @@ #ifdef TSRM_WIN32 #include #include "tsrm_win32.h" +# ifndef IO_REPARSE_TAG_SYMLINK +# define IO_REPARSE_TAG_SYMLINK 0xA000000C +# endif #endif #ifdef NETWARE @@ -137,6 +140,42 @@ static int php_check_dots(const char *element, int n) #ifdef TSRM_WIN32 +#ifdef CTL_CODE +#undef CTL_CODE +#endif +#define CTL_CODE(DeviceType,Function,Method,Access) (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#define FILE_DEVICE_FILE_SYSTEM 0x00000009 +#define METHOD_BUFFERED 0 +#define FILE_ANY_ACCESS 0 +#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) + +typedef struct { + unsigned long ReparseTag; + unsigned short ReparseDataLength; + unsigned short Reserved; + union { + struct { + unsigned short SubstituteNameOffset; + unsigned short SubstituteNameLength; + unsigned short PrintNameOffset; + unsigned short PrintNameLength; + unsigned long Flags; + wchar_t ReparseTarget[1]; + } SymbolicLinkReparseBuffer; + struct { + unsigned short SubstituteNameOffset; + unsigned short SubstituteNameLength; + unsigned short PrintNameOffset; + unsigned short PrintNameLength; + wchar_t ReparseTarget[1]; + } MountPointReparseBuffer; + struct { + unsigned char ReparseTarget[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER; + #define SECS_BETWEEN_EPOCHS (__int64)11644473600 #define SECS_TO_100NS (__int64)10000000 static inline time_t FileTimeToUnixTime(const FILETIME FileTime) @@ -471,6 +510,12 @@ static inline void realpath_cache_add(const char *path, int path_len, const char } bucket->realpath_len = realpath_len; bucket->is_dir = is_dir; +#ifdef PHP_WIN32 + bucket->is_rvalid = 0; + bucket->is_readable = 0; + bucket->is_wvalid = 0; + bucket->is_writable = 0; +#endif bucket->expires = t + CWDG(realpath_cache_ttl); n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); bucket->next = CWDG(realpath_cache)[n]; @@ -503,6 +548,12 @@ static inline realpath_cache_bucket* realpath_cache_find(const char *path, int p } /* }}} */ +CWD_API realpath_cache_bucket* realpath_cache_lookup(const char *path, int path_len, time_t t TSRMLS_DC) /* {{{ */ +{ + return realpath_cache_find(path, path_len, t TSRMLS_CC); +} +/* }}} */ + #undef LINK_MAX #define LINK_MAX 32 @@ -518,7 +569,8 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i #endif realpath_cache_bucket *bucket; char *tmp; - TSRM_ALLOCA_FLAG(use_heap); + TSRM_ALLOCA_FLAG(use_heap) + TSRM_ALLOCA_FLAG(use_heap_large) while (1) { if (len <= start) { @@ -606,16 +658,103 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i /* continue resolution anyway but don't save result in the cache */ save = 0; } + if (save) { - directory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - if (is_dir && !directory) { - /* not a directory */ - FindClose(hFind); - return -1; - } + FindClose(hFind); } + tmp = tsrm_do_alloca(len+1, use_heap); memcpy(tmp, path, len+1); + + if(save && (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + /* File is a reparse point. Get the target */ + HANDLE hLink = NULL; + REPARSE_DATA_BUFFER * pbuffer; + unsigned int retlength = 0, rname_off = 0; + int bufindex = 0, rname_len = 0, isabsolute = 0; + wchar_t * reparsetarget; + + if(++(*ll) > LINK_MAX) { + return -1; + } + + hLink = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL); + if(hLink == INVALID_HANDLE_VALUE) { + return -1; + } + + pbuffer = (REPARSE_DATA_BUFFER *)tsrm_do_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, use_heap_large); + if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) { + tsrm_free_alloca(pbuffer, use_heap_large); + CloseHandle(hLink); + return -1; + } + + CloseHandle(hLink); + + if(pbuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + rname_len = pbuffer->SymbolicLinkReparseBuffer.PrintNameLength/2; + rname_off = pbuffer->SymbolicLinkReparseBuffer.PrintNameOffset/2; + reparsetarget = pbuffer->SymbolicLinkReparseBuffer.ReparseTarget; + isabsolute = (pbuffer->SymbolicLinkReparseBuffer.Flags == 0) ? 1 : 0; + } + else if(pbuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { + rname_len = pbuffer->MountPointReparseBuffer.PrintNameLength/2; + rname_off = pbuffer->MountPointReparseBuffer.PrintNameOffset/2; + reparsetarget = pbuffer->MountPointReparseBuffer.ReparseTarget; + isabsolute = 1; + } + else { + tsrm_free_alloca(pbuffer, use_heap_large); + return -1; + } + + /* Convert wide string to narrow string */ + for(bufindex = 0; bufindex < rname_len; bufindex++) { + *(path + bufindex) = (char)(reparsetarget[rname_off + bufindex]); + } + + *(path + bufindex) = 0; + tsrm_free_alloca(pbuffer, use_heap_large); + j = bufindex; + + if(isabsolute == 1) { + /* use_realpath is 0 in the call below coz path is absolute*/ + j = tsrm_realpath_r(path, 0, j, ll, t, 0, is_dir, &directory TSRMLS_CC); + if(j < 0) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + } + else { + if(i + j >= MAXPATHLEN - 1) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + + memmove(path+i, path, j+1); + memcpy(path, tmp, i-1); + path[i-1] = DEFAULT_SLASH; + j = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory TSRMLS_CC); + if(j < 0) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + } + + if(link_is_dir) { + *link_is_dir = directory; + } + } + else { + if (save) { + directory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (is_dir && !directory) { + /* not a directory */ + return -1; + } + } + #elif defined(NETWARE) save = 0; tmp = tsrm_do_alloca(len+1, use_heap); @@ -687,19 +826,18 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i #ifdef TSRM_WIN32 if (j < 0 || j + len - i >= MAXPATHLEN-1) { tsrm_free_alloca(tmp, use_heap); - if (save) FindClose(hFind); return -1; } if (save) { i = strlen(data.cFileName); memcpy(path+j, data.cFileName, i+1); j += i; - FindClose(hFind); } else { /* use the original file or directory name as it wasn't found */ memcpy(path+j, tmp+i, len-i+1); j += (len-i); } + } #else if (j < 0 || j + len - i >= MAXPATHLEN-1) { tsrm_free_alloca(tmp, use_heap); diff --git a/TSRM/tsrm_virtual_cwd.h b/TSRM/tsrm_virtual_cwd.h index 49602380d3..cc7f7f61ea 100644 --- a/TSRM/tsrm_virtual_cwd.h +++ b/TSRM/tsrm_virtual_cwd.h @@ -227,6 +227,12 @@ typedef struct _realpath_cache_bucket { int realpath_len; int is_dir; time_t expires; +#ifdef PHP_WIN32 + unsigned char is_rvalid; + unsigned char is_readable; + unsigned char is_wvalid; + unsigned char is_writable; +#endif struct _realpath_cache_bucket *next; } realpath_cache_bucket; @@ -248,6 +254,7 @@ extern virtual_cwd_globals cwd_globals; CWD_API void realpath_cache_clean(TSRMLS_D); CWD_API void realpath_cache_del(const char *path, int path_len TSRMLS_DC); +CWD_API realpath_cache_bucket* realpath_cache_lookup(const char *path, int path_len, time_t t TSRMLS_DC); /* The actual macros to be used in programs using TSRM * If the program defines VIRTUAL_DIR it will use the diff --git a/TSRM/tsrm_win32.c b/TSRM/tsrm_win32.c index ae18f39ec2..f49a711b2e 100644 --- a/TSRM/tsrm_win32.c +++ b/TSRM/tsrm_win32.c @@ -33,6 +33,7 @@ #ifdef TSRM_WIN32 #include "tsrm_win32.h" +#include "tsrm_virtual_cwd.h" #ifdef ZTS static ts_rsrc_id win32_globals_id; @@ -42,12 +43,25 @@ static tsrm_win32_globals win32_globals; static void tsrm_win32_ctor(tsrm_win32_globals *globals TSRMLS_DC) { + HANDLE process_token = NULL; + globals->process = NULL; globals->shm = NULL; globals->process_size = 0; globals->shm_size = 0; globals->comspec = _strdup((GetVersion()<0x80000000)?"cmd.exe":"command.com"); globals->impersonation_token = NULL; + + /* Access check requires impersonation token. Create a duplicate token. */ + if(OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &process_token)) { + DuplicateToken(process_token, SecurityImpersonation, &globals->impersonation_token); + } + + /* impersonation_token will be closed when the process dies */ + if(process_token != NULL) { + CloseHandle(process_token); + process_token = NULL; + } } static void tsrm_win32_dtor(tsrm_win32_globals *globals TSRMLS_DC) @@ -102,6 +116,11 @@ TSRM_API int tsrm_win32_access(const char *pathname, int mode) BYTE * psec_desc = NULL; BOOL fAccess = FALSE; HANDLE process_token = NULL; + + realpath_cache_bucket * bucket = NULL; + char * real_path = NULL; + time_t t; + TSRMLS_FETCH(); if (mode == 1 /*X_OK*/) { @@ -109,18 +128,60 @@ TSRM_API int tsrm_win32_access(const char *pathname, int mode) return GetBinaryType(pathname, &type) ? 0 : -1; } else { if(access(pathname, mode)) { - return errno; + return errno; } - /* Do a full access check because access() will only check read-only attribute */ - if(mode == 0 || mode > 6) { + if(!IS_ABSOLUTE_PATH(pathname, strlen(pathname)+1)) { + real_path = (char *)malloc(MAX_PATH); + if(tsrm_realpath(pathname, real_path TSRMLS_CC) == NULL) { + goto Finished; + } + pathname = real_path; + } + + if (CWDG(realpath_cache_size_limit)) { + t = time(0); + bucket = realpath_cache_lookup(pathname, strlen(pathname), t TSRMLS_CC); + 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(MAX_PATH); + if(tsrm_realpath(pathname, real_path TSRMLS_CC) != NULL) { + pathname = real_path; + bucket = realpath_cache_lookup(pathname, strlen(pathname), t TSRMLS_CC); + } + } + } + + /* 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; - } else if(mode <= 2) { - desired_access = FILE_GENERIC_WRITE; - } else if(mode <= 4) { - desired_access = FILE_GENERIC_READ; - } else { // if(mode <= 6) - desired_access = FILE_GENERIC_READ | FILE_GENERIC_WRITE; + } 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; } /* Get size of security buffer. Call is expected to fail */ @@ -134,35 +195,33 @@ TSRM_API int tsrm_win32_access(const char *pathname, int mode) goto Finished; } - if(TWG(impersonation_token) == NULL) { - - if(!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &process_token)) { - goto Finished; - } - - /* Access check requires impersonation token. Create a duplicate token. */ - if(!DuplicateToken(process_token, SecurityImpersonation, &TWG(impersonation_token))) { - goto Finished; - } - } - if(!AccessCheck((PSECURITY_DESCRIPTOR)psec_desc, TWG(impersonation_token), desired_access, &gen_map, &privilege_set, &priv_set_length, &granted_access, &fAccess)) { goto Finished; } -Finished: - - /* impersonation_token will be closed when the process dies */ - if(process_token != NULL) { - CloseHandle(process_token); - process_token = NULL; + /* Keep the result in realpath_cache */ + if(bucket != NULL) { + if(desired_access == FILE_GENERIC_READ) { + bucket->is_rvalid = 1; + bucket->is_readable = fAccess; + } + else if(desired_access == FILE_GENERIC_WRITE) { + bucket->is_wvalid = 1; + bucket->is_writable = fAccess; + } } +Finished: if(psec_desc != NULL) { free(psec_desc); psec_desc = NULL; } + if(real_path != NULL) { + free(real_path); + real_path = NULL; + } + if(fAccess == FALSE) { errno = EACCES; return errno; @@ -326,7 +385,7 @@ TSRM_API int pclose(FILE *stream) } fflush(process->stream); - fclose(process->stream); + fclose(process->stream); WaitForSingleObject(process->prochnd, INFINITE); GetExitCodeProcess(process->prochnd, &termstat); -- 2.40.0