/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name: SafePath.c (WinSAFER Path Comparison) Abstract: This module implements the WinSAFER APIs that evaluate the system policies to determine which Authorization Level has been configured to apply restrictions for a specified application or code library. Author: Jeffrey Lawson (JLawson) - Nov 1999 Environment: User mode only. Exported Functions: CodeAuthzpCompareImagePath Revision History: Created - Nov 1999 --*/ #include "pch.h" #pragma hdrstop #include #include #include "saferp.h" // // Define the following value to use the "new" comparison logic, // that includes asterick and question mark matching. // #define USE_NEW_WILDCARD_EVALUATION // // Convenient macros for doing filename pattern matching. // #define IS_UCASE_CHARS_EQUAL_U(ch1, ch2) (((ch1) == (ch2)) || (RtlUpcaseUnicodeChar(ch1) == RtlUpcaseUnicodeChar(ch2))) #define IS_PATH_SEPARATOR_U(ch) (((ch) == L'\\') || ((ch) == L'/')) #define IS_WILDCARD_CHAR_U(ch) ((ch) == L'*') #define IS_QUESTION_CHAR_U(ch) ((ch) == L'?') #define IS_DOT_CHAR_U(ch) ((ch) == L'.') FORCEINLINE LPCWSTR CodeAuthzpFindSlash ( IN LPCWSTR string, IN USHORT length ) /*++ Routine Description: Returns a pointer to the first instance of a forward or backward slash within the specified string buffer. Arguments: string - length - Return Value: Returns NULL if no backslashes or forward-slashes were found within the string. Otherwise returns a pointer to the matching char. --*/ { while (length-- > 0) { if (IS_PATH_SEPARATOR_U(*string)) return string; string++; } return NULL; } #ifdef USE_NEW_WILDCARD_EVALUATION LONG NTAPI __CodeAuthzpCompareImagePathHelper( IN LPCWSTR wild, IN USHORT wildlen, IN LPCWSTR actual, IN USHORT actuallen ) /*++ Routine Description: Evaluates a wildcard pattern against a specified pathname and indicates if they match. Arguments: wild - wildlen - actual - actuallen - Return Value: 0 = no match -1 = match exactly 1 = match with wildcard --*/ { LONG lMatchResult = -1; ASSERT(ARGUMENT_PRESENT(wild) && !CodeAuthzpFindSlash(wild, wildlen)); ASSERT(ARGUMENT_PRESENT(actual) && !CodeAuthzpFindSlash(actual, actuallen)); for (;;) { // Check for terminating conditions. if (wildlen == 0) { if (actuallen == 0) { return lMatchResult; } else { ASSERT(actuallen > 0); return 0; } } // Evaluate the wildcard pattern. if (IS_WILDCARD_CHAR_U(*wild)) { USHORT matchcount; // Skip past the asterick (possibly multiple). do { wild++; wildlen--; } while ( wildlen > 0 && IS_WILDCARD_CHAR_U(*wild) ); // Try expanding the asterick to be zero or more chars. for (matchcount = 0; ; matchcount++) { if (matchcount > actuallen) { return 0; // match failed. } if (0 != __CodeAuthzpCompareImagePathHelper( wild, wildlen, &actual[matchcount], actuallen - matchcount)) { actual += matchcount; actuallen -= matchcount; break; } } // We've encountered a wildcard char, so remember // that this is no longer an "exact" match. lMatchResult = 1; } else if (IS_QUESTION_CHAR_U(*wild)) { // Question marks will match any single character, except // periods. Question marks will also match nothing when // we are already at the end of the filename or segment. if (actuallen > 0 && !IS_DOT_CHAR_U(*actual)) { actual++; actuallen--; } wild++; wildlen--; // We've encountered a wildcard char, so remember // that this is no longer an "exact" match. lMatchResult = 1; } else { if (actuallen < 1 || !IS_UCASE_CHARS_EQUAL_U(*wild, *actual)) { return 0; } wild++; wildlen--; actual++; actuallen--; } } } LONG NTAPI CodeAuthzpCompareUnicodeImagePath( IN PCUNICODE_STRING wildcard, IN PCUNICODE_STRING actual ) /*++ Routine Description: Evaluates a wildcard pattern against a specified pathname and indicates if they match. Arguments: wildcard - actual - Return Value: Returns 0 if the path fragment does not match the specified imagepath. Returns -1 if the fragment matches the imagepath _exactly_! Otherwise returns a postive integer representing the "depth" of the the match (number of matching subdirectories). Greater values indicate a "deeper" directory match. --*/ { USHORT wildindex = 0, actualindex = 0; LONG matchquality = 0; BOOLEAN bNoWildcardsFound = TRUE; ASSERT(ARGUMENT_PRESENT(wildcard) && wildcard->Buffer != NULL); ASSERT(ARGUMENT_PRESENT(actual) && actual->Buffer != NULL); for (;;) { ASSERT(wildindex <= wildcard->Length / sizeof(WCHAR)); ASSERT(actualindex <= actual->Length / sizeof(WCHAR)); if (wildindex == wildcard->Length / sizeof(WCHAR)) { // We've reached the end of the wildcard but the actual string has // not ended. if (actualindex < actual->Length / sizeof(WCHAR)) { return matchquality; } // The wildcard matched the filename but with inexact matches. // Return one more than the actual depth so that we can handle // non-qualified path matches as worse then these. if (!bNoWildcardsFound) { return (matchquality + 1); } ASSERT(wildindex == wildcard->Length / sizeof(WCHAR)); return -1; // exact match. } else if (IS_PATH_SEPARATOR_U(wildcard->Buffer[wildindex])) { if (!IS_PATH_SEPARATOR_U(actual->Buffer[actualindex])) { return 0; // no match } // Skip forward to the start of the next component. do { wildindex++; } while ( wildindex < wildcard->Length / sizeof(WCHAR) && IS_PATH_SEPARATOR_U(wildcard->Buffer[wildindex]) ); // Skip forward to the start of the next component. do { actualindex++; } while ( actualindex < actual->Length / sizeof(WCHAR) && IS_PATH_SEPARATOR_U(actual->Buffer[actualindex]) ); } else { USHORT wildlen = 0, actuallen = 0; // Count the length of this component of the wildcard. while (wildindex + wildlen < (USHORT) (wildcard->Length / sizeof(WCHAR)) && !IS_PATH_SEPARATOR_U(wildcard->Buffer[wildindex + wildlen])) { wildlen++; } ASSERT(wildlen > 0); // Count the length of this component of the actual path. while (actualindex + actuallen < (USHORT) (actual->Length / sizeof(WCHAR)) && !IS_PATH_SEPARATOR_U(actual->Buffer[actualindex + actuallen])) { actuallen++; } // Otherwise require that this component matches. switch (__CodeAuthzpCompareImagePathHelper( &wildcard->Buffer[wildindex], wildlen, &actual->Buffer[actualindex], actuallen)) { case 0: // fails to match return 0; case -1: // matches exactly without wildcards break; default: // matches with wildcard expansion. bNoWildcardsFound = FALSE; break; } // Increment pointers for next component. wildindex += wildlen; actualindex += actuallen; matchquality++; } } } LONG NTAPI CodeAuthzpCompareImagePath( IN LPCWSTR szPathFragment, IN LPCWSTR szFullImagePath ) /*++ Routine Description: Evaluates a wildcard pattern against a specified pathname and indicates if they match. Arguments: szPathFragment - szFullImagePath - Return Value: Returns 0 if the path fragment does not match the specified imagepath. Returns -1 if the fragment matches the imagepath _exactly_! Otherwise returns a postive integer representing the "depth" of the the match (number of matching subdirectories). Greater values indicate a "deeper" directory match. --*/ { UNICODE_STRING UnicodePathFragment; UNICODE_STRING UnicodeFullImagePath; ULONG i = 0; USHORT Len = 0; LONG lMatchDepth = 0; RtlInitUnicodeString(&UnicodePathFragment, szPathFragment); RtlInitUnicodeString(&UnicodeFullImagePath, szFullImagePath); lMatchDepth = CodeAuthzpCompareUnicodeImagePath( &UnicodePathFragment, &UnicodeFullImagePath); // We did not get a match for fully qualified name. Let's try for just a // basename match. if (!lMatchDepth) { // if the rule has a '\' in it, it's not a basename match rule. // We only check for filename.ext rules allowing wildcards. if (wcschr(szPathFragment, L'\\')) { return 0; } Len = (UnicodeFullImagePath.Length/sizeof(WCHAR)) -1; // Skip from rightmost character to the the character just after the // last '\', if any or to the beginning of the string in absence of '\'. while (Len > 0 && szFullImagePath[Len] != L'\\') { Len--; } // A '\' exists. Move one character to the right. if (szFullImagePath[Len] == L'\\') { Len++; } // Check if there is a match of the file basename with the rule. We have // already checked that the rule does not have '\'. switch (__CodeAuthzpCompareImagePathHelper( szPathFragment, UnicodePathFragment.Length/sizeof(WCHAR), szFullImagePath+Len, (UnicodeFullImagePath.Length/sizeof(WCHAR))-Len)) { case 0: // fails to match return 0; case -1: // matches exactly without wildcards default: // matches with wildcard expansion. // We treat exact matches the same as inexact matches here. // Thus, abc.exe is == a*.exe = *.exe. // Skip to the first non-'\' character. while ((szFullImagePath[i] == L'\\') && (szFullImagePath[i] != L'\0')) { i++; } // This string is bogus. It only has 0 or more '\'s in it. if (szFullImagePath[i] == L'\0') { return 0; } // Return the depth of the tree. lMatchDepth = 1; while (TRUE) { // Skip to the first '\' while not end of string. while ((szFullImagePath[i] != L'\\') && (szFullImagePath[i] != L'\0')) { i++; } // We are at the end of the string. Return the depth. if (szFullImagePath[i] == L'\0') { return lMatchDepth; } // Skip to the first non-'\' while not end of string. while ((szFullImagePath[i] == L'\\') && (szFullImagePath[i] != L'\0')) { i++; } // We are at a non-'\' character. Increment the depth. lMatchDepth++; } // Should never get here. ASSERT(FALSE); } } return lMatchDepth; } #else // #ifdef USE_NEW_WILDCARD_EVALUATION LONG NTAPI CodeAuthzpCompareImagePath( IN LPCWSTR szPathFragment, IN LPCWSTR szFullImagePath ) /*++ Routine Description: Evaluates a wildcard pattern against a specified pathname and indicates if they match. Arguments: szPathFragment - szFullImagePath - Return Value: Returns 0 if the path fragment does not match the specified imagepath. Returns -1 if the fragment matches the imagepath _exactly_! Otherwise returns a postive integer representing the "depth" of the the match (number of matching subdirectories). Greater values indicate a "deeper" directory match. --*/ { LONG MatchDepth = 0; BOOLEAN bLastWasSlash = TRUE; LPCWSTR pFragment = szPathFragment; LPCWSTR pImage = szFullImagePath; // // Verify that our arguments were all supplied. // ASSERT(ARGUMENT_PRESENT(pFragment) && ARGUMENT_PRESENT(pImage)); if (!*pFragment || !*pImage) return 0; // empty strings. // // Perform the actual comparison loop. // for (;;) { if (!*pFragment) { // We have reached the string terminator at the end of the // wildcard fragment. If this was also the end of the // actual filename, then this is a precise match. Otherwise // we'll only consider this a positive partial match if this // occurred on a path-separator boundary. if (!*pImage) return -1; // matched exactly. else if (bLastWasSlash) break; else if (IS_PATH_SEPARATOR_U(*pImage)) break; else return 0; // did not match. } else if (!*pImage) { // We have reached the end of the actual filename, but have // not yet found the end of the wildcard fragment. return 0; // did not match. } else if (!IS_UCASE_CHARS_EQUAL_U(*pFragment, *pImage)) { // The two characters were unequal. However, this condition // might occur if multiple path separators occur in one and // not the other, so explicitly absorb multiple slashes. if (bLastWasSlash) { if (IS_PATH_SEPARATOR_U(*pFragment)) { pFragment++; continue; } else if (IS_PATH_SEPARATOR_U(*pImage)) { pImage++; continue; } } return 0; // did not match. } else { // Both characters matched. Remember if they were slashes. // If this is a transition to a non-separator portion of the // filename, then increment our depth counter. if (IS_PATH_SEPARATOR_U(*pFragment)) { bLastWasSlash = TRUE; } else { if (bLastWasSlash) { MatchDepth++; bLastWasSlash = FALSE; } } } pFragment++; pImage++; } return MatchDepth; } #endif //#ifdef USE_NEW_WILDCARD_EVALUATION