From 0f8885eba3047270f8d0d21e93cddc8d2358807e Mon Sep 17 00:00:00 2001 From: vincentdarley Date: Thu, 9 Mar 2006 11:30:28 +0000 Subject: fix to file writable in certain XP directories --- ChangeLog | 5 ++ win/tclWinFile.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 19b8961..db0af69 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2006-03-09 Vince Darley + + * win/tclWinFile.c: fix to 'file writable' in certain XP + directories. Thanks to fvogel and jfg. [Patch 1344540] + 2006-03-08 Don Porter * generic/tclExecute.c: Complete missing bit of TIP 215 implementation diff --git a/win/tclWinFile.c b/win/tclWinFile.c index f65e1c1..1284671 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinFile.c,v 1.82 2006/01/12 16:19:04 vincentdarley Exp $ + * RCS: @(#) $Id: tclWinFile.c,v 1.83 2006/03/09 11:30:29 vincentdarley Exp $ */ /* #define _WIN32_WINNT 0x0500 */ @@ -188,6 +188,7 @@ static int NativeStat(CONST TCHAR *path, Tcl_StatBuf *statPtr, int checkLinks); static unsigned short NativeStatMode(DWORD attr, int checkLinks, int isExec); static int NativeIsExec(CONST TCHAR *path); +static int NativeIsWritable(DWORD attr, CONST TCHAR *path); static int NativeReadReparse(CONST TCHAR *LinkDirectory, REPARSE_DATA_BUFFER* buffer); static int NativeWriteReparse(CONST TCHAR *LinkDirectory, @@ -1546,7 +1547,7 @@ NativeAccess( return -1; } - if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY)) { + if ((mode & W_OK) && !NativeIsWritable(attr, nativePath)) { /* * File is not writable. */ @@ -1789,6 +1790,156 @@ NativeIsExec( /* *---------------------------------------------------------------------- * + * NativeIsWritable -- + * + * Determine if a path is writable. On Windows this requires more than a + * simple read only flag check for directories because Windows uses this + * as a flag to customize the folder options. [Bug 1193497] + * + * Results: + * 1 = writable, 0 = not. + * + * Side effects: + * May smash the last error value (errors are just reported by this + * function as non-writable files). + * + *---------------------------------------------------------------------- + */ +static int +NativeIsWritable( + DWORD attr, + CONST TCHAR *nativePath) +{ + BYTE *secDescPtr = 0; + DWORD secDescLen = 0; + HANDLE hToken = NULL; + BOOL isWritable = FALSE; + + /* + * One time initialization, dynamically load Windows NT features + */ + + static const SECURITY_INFORMATION infoBits = OWNER_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + + static BOOL (WINAPI *getFileSecurityProc)(LPCSTR, SECURITY_INFORMATION, + PSECURITY_DESCRIPTOR, DWORD, LPDWORD); + static BOOL (WINAPI *openProcessTokenProc)(HANDLE, DWORD, PHANDLE); + static BOOL (WINAPI *duplicateTokenProc)(HANDLE, SECURITY_IMPERSONATION_LEVEL, + PHANDLE); + static VOID (WINAPI *mapGenericMaskProc)(PDWORD, PGENERIC_MAPPING); + static BOOL (WINAPI *accessCheckProc)(PSECURITY_DESCRIPTOR, HANDLE, DWORD, + PGENERIC_MAPPING, PPRIVILEGE_SET, LPDWORD, LPDWORD, LPBOOL); + + static int initialized = 0; + + if (!initialized) { + TCL_DECLARE_MUTEX(writableMutex) + Tcl_MutexLock(&writableMutex); + if (!initialized) { + HINSTANCE hInstance = LoadLibrary("Advapi32"); + if (hInstance != NULL) { + getFileSecurityProc = (BOOL (WINAPI *)(LPCSTR, + SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, LPDWORD)) + GetProcAddress(hInstance, tclWinProcs->useWide ? + "GetFileSecurityW" : "GetFileSecurityA"); + openProcessTokenProc = (BOOL (WINAPI *)(HANDLE, DWORD, + PHANDLE)) GetProcAddress(hInstance, "OpenProcessToken"); + duplicateTokenProc = (BOOL (WINAPI *)(HANDLE, + SECURITY_IMPERSONATION_LEVEL, PHANDLE)) + GetProcAddress(hInstance, "DuplicateToken"); + mapGenericMaskProc = (VOID (WINAPI *)(PDWORD, + PGENERIC_MAPPING)) GetProcAddress(hInstance, + "MapGenericMask"); + accessCheckProc = (BOOL (WINAPI *)(PSECURITY_DESCRIPTOR, + HANDLE, DWORD, PGENERIC_MAPPING, PPRIVILEGE_SET, + LPDWORD, LPDWORD, LPBOOL)) GetProcAddress(hInstance, + "AccessCheck"); + if (getFileSecurityProc && openProcessTokenProc + && duplicateTokenProc && mapGenericMaskProc + && accessCheckProc) { + initialized = 1; + } + } + if (!initialized) + initialized = -1; + } + Tcl_MutexUnlock(&writableMutex); + } + + /* + * Windows NT features not available, default back to readonly flag + */ + if (initialized < 0) + return attr & FILE_ATTRIBUTE_READONLY; + + /* + * Read the security descriptor for the file or directory. Note the + * first call obtains the size of the security descriptor. + */ + + if (!getFileSecurityProc(nativePath, infoBits, NULL, 0, &secDescLen)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + DWORD secDescLen2 = 0; + secDescPtr = (BYTE *) ckalloc(secDescLen); + if (!getFileSecurityProc(nativePath, infoBits, secDescPtr, + secDescLen, &secDescLen2) + || (secDescLen < secDescLen2)) { + goto done; + } + } else { + goto done; + } + } + + /* + * Get the security identity of the process and (if successful) use it + * with the security descriptor of the file to find out if the file really + * is writable. + */ + if (openProcessTokenProc(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken)) { + HANDLE hIToken = NULL; + BOOL status = duplicateTokenProc(hToken, SecurityIdentification, + &hIToken); + + CloseHandle(hToken); + if (status) { + PRIVILEGE_SET privilegeSet; + DWORD privilegeSetLength = sizeof(privilegeSet); + DWORD granted; /* unused */ + DWORD accessDesired = ACCESS_WRITE; + + /* Initialize generic mapping structure to map all. */ + GENERIC_MAPPING GenericMapping; + memset(&GenericMapping, 0xff, sizeof(GENERIC_MAPPING)); + GenericMapping.GenericRead = ACCESS_READ; + GenericMapping.GenericWrite = ACCESS_WRITE; + GenericMapping.GenericExecute = 0; + GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE; + mapGenericMaskProc(&accessDesired, &GenericMapping); + + accessCheckProc(secDescPtr, hIToken, accessDesired, + &GenericMapping, &privilegeSet, &privilegeSetLength, &granted, + &isWritable); + CloseHandle(hIToken); + } + } + + /* + * Release any temporary space we may be using and convert the writability + * value into a boolean for return. + */ + + done: + if (secDescPtr) { + ckfree(secDescPtr); + } + return isWritable != 0; +} + +/* + *---------------------------------------------------------------------- + * * TclpObjChdir -- * * This function replaces the library version of chdir(). -- cgit v0.12