From 2acec63b2ed75bf4b71ad257db573c4b8f9639e7 Mon Sep 17 00:00:00 2001 From: tumagonx Date: Tue, 8 Aug 2017 10:54:53 +0700 Subject: initial commit --- policy.c | 3243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3243 insertions(+) create mode 100644 policy.c (limited to 'policy.c') diff --git a/policy.c b/policy.c new file mode 100644 index 0000000..be08a81 --- /dev/null +++ b/policy.c @@ -0,0 +1,3243 @@ +/* + * Copyright (c) 2004 Security Architects Corporation. All rights reserved. + * + * Module Name: + * + * policy.c + * + * Abstract: + * + * This module implements various security policy parsing and enforcement routines. + * + * Author: + * + * Eugene Tsyrklevich 16-Feb-2004 + * + * Revision History: + * + * None. + */ + +// XXX rename all funcs as SpYYY ? (same for other modules?) + +#include +#include "policy.h" +#include "pathproc.h" +#include "procname.h" +#include "hookproc.h" +#include "media.h" +#include "learn.h" +#include "misc.h" +#include "i386.h" + + +#include "process.h" +#include "log.h" + + +BOOLEAN PolicyParseRule(OUT PSECURITY_POLICY pSecPolicy, IN PCHAR rule, OUT BOOLEAN *Critical); +BOOLEAN PolicyParsePolicyRule(OUT PSECURITY_POLICY pSecPolicy, IN PCHAR Operation, IN PCHAR rule, OUT BOOLEAN *Critical); +BOOLEAN PolicyParseObjectRule(PSECURITY_POLICY pSecPolicy, RULE_TYPE RuleType, PCHAR Operation, PCHAR rule); +BOOLEAN PolicyParseSyscallRule(PSECURITY_POLICY pSecPolicy, PCHAR SyscallName, PCHAR rule); +BOOLEAN PolicyParseProtectionRule(PSECURITY_POLICY pSecPolicy, PCHAR Operation, PCHAR rule); +BOOLEAN PolicyParseMediaRule(PSECURITY_POLICY pSecPolicy, PCHAR Operation, PCHAR rule); + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (INIT, InitPolicy) +#pragma alloc_text (PAGE, PolicyPostBootup) +#pragma alloc_text (PAGE, PolicyRemove) +#endif + + +/* + * example: + * + * SystemRoot - \device\harddisk1\windows + * SystemRootUnresolved - c:\windows + * SystemRootDirectory - \windows + * CDrive - \device\harddisk1 + */ + +CHAR SystemDrive, SystemRoot[MAX_PATH], SystemRootUnresolved[MAX_PATH], *SystemRootDirectory, CDrive[MAX_PATH]; +USHORT SystemRootLength = 0, SystemRootUnresolvedLength = 0, SystemRootDirectoryLength = 0, CDriveLength = 0; + +USHORT gPolicyLineNumber; +PWSTR gPolicyFilename, gFilePath; + +// to be portable on 32 & 64 bit platforms +ULONG NumberOfBitsInUlong, UlongBitShift; + +/* LoadPolicy() can be used by only one thread at a time due to use of global variables */ +KMUTEX LoadPolicyMutex; + +/* Global Security Policy */ +SECURITY_POLICY gSecPolicy; + + + +/* + * InitPolicy() + * + * Description: + * Initialize the policy engine. Load the global policy. + * + * NOTE: Called once during driver initialization (DriverEntry()). + * + * Parameters: + * None. + * + * Returns: + * TRUE if everything is OK, FALSE if failed. + */ + +BOOLEAN +InitPolicy() +{ + NumberOfBitsInUlong = sizeof(ULONG) * 8; + UlongBitShift = sizeof(ULONG) == 4 ? 5 : 6; + + + /* gets reinitialized correctly once bootup is complete (see PolicyPostBootup) */ + SystemDrive = 'C'; + + if (ReadSymlinkValue(L"\\SystemRoot", SystemRootUnresolved, MAX_PATH) == FALSE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("InitPolicy: ReadSymlinkValue failed\n")); + return FALSE; + } + + SystemRootUnresolvedLength = (USHORT) strlen(SystemRootUnresolved); + + ResolveFilename(SystemRootUnresolved, SystemRoot, MAX_PATH); + + + /* extract the system directory name by itself (i.e. \windows) */ + SystemRootDirectory = strrchr(SystemRoot, '\\'); + + if (SystemRootDirectory == NULL) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("InitPolicy: SystemRootDirectory is NULL\n")); + return FALSE; + } + + SystemRootDirectoryLength = (USHORT) strlen(SystemRootDirectory); + + + if (ReadSymlinkValue(L"\\??\\C:", CDrive, MAX_PATH) == FALSE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("InitPolicy: Failed to open C: symbolic link\n")); + return FALSE; + } + + CDriveLength = (USHORT) strlen(CDrive); + + + if (PolicyPostBootup() == FALSE) + { + /* + * if boot process is not complete yet then we cannot get SystemRootUnresolved (i.e. c:\windows) + * because parts of registry are not initialized yet (see PolicyPostBootup) + * + * In that case, try to assemble SystemRootUnresolved manually + */ + + SystemRootUnresolved[0] = SystemDrive; + SystemRootUnresolved[1] = ':'; + + strcpy(SystemRootUnresolved + 2, SystemRootDirectory); + } + + + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("InitPolicy: SystemRoot=%s (%s, %s)\n", SystemRoot, SystemRootUnresolved, SystemRootDirectory)); + + + KeInitializeMutex(&LoadPolicyMutex, 0); + + RtlZeroMemory(&gSecPolicy, sizeof(gSecPolicy)); + + KeInitializeSpinLock(&gSecPolicy.SpinLock); + + + if (LearningMode == TRUE) + + return TRUE; + + + if (FindAndLoadSecurityPolicy(&gSecPolicy, L"computer", NULL) == FALSE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_WARNING, ("InitPolicy: LoadSecurityPolicy(computer.policy) failed\n")); + gSecPolicy.DefaultPolicyAction = ACTION_PERMIT_DEFAULT; + } + + + if (gSecPolicy.DefaultPolicyAction != ACTION_PERMIT_DEFAULT) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_WARNING, ("InitPolicy: Global policy default action must be permit\n")); + gSecPolicy.DefaultPolicyAction = ACTION_PERMIT_DEFAULT; + } + + + return TRUE; +} + + + +/* + * PolicyPostBootup() + * + * Description: + * Finish initializing system variables once the bootup process is complete. + * We are unable to read the SystemRoot registry value before the bootup is complete since + * that part of the registry has not been initialized yet. + * + * Parameters: + * None. + * + * Returns: + * TRUE to indicate success, FALSE if failed. + */ + +BOOLEAN +PolicyPostBootup() +{ + ASSERT(BootingUp == FALSE); + + + if (ReadStringRegistryValueA(L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion", L"SystemRoot", SystemRootUnresolved, MAX_PATH) == FALSE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("PolicyPostBootup: Failed to open SystemRoot registry key\n")); + return FALSE; + } + + SystemRootUnresolvedLength = (USHORT) strlen(SystemRootUnresolved); + + SystemDrive = (CHAR) toupper(SystemRootUnresolved[0]); + + + return TRUE; +} + + + +/* + * PolicyRemove() + * + * Description: + * Shutdown the policy engine. Delete the global policy + * + * Parameters: + * None. + * + * Returns: + * Nothing. + */ + +void +PolicyRemove() +{ + PolicyDelete(&gSecPolicy); +} + + + +/* + * PolicyDelete() + * + * Description: + * Delete a security policy. Free all the rules associated with a policy. + * + * Parameters: + * pSecPolicy - pointer to a security policy to delete. + * + * Returns: + * Nothing. + */ + +void +PolicyDelete(IN PSECURITY_POLICY pSecPolicy) +{ + PPOLICY_RULE r, tmp; + KIRQL irql; + UCHAR i; + + + if (pSecPolicy == NULL) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("PolicyDelete: pSecPolicy is NULL\n")); + return; + } + + + if (pSecPolicy->Initialized == FALSE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("PolicyDelete: pSecPolicy is not initialized\n")); + return; + } + + + KeAcquireSpinLock(&pSecPolicy->SpinLock, &irql); + + for (i = 0; i < RULE_LASTONE; i++) + { + r = pSecPolicy->RuleList[i]; + + while (r) + { + tmp = r; + r = (PPOLICY_RULE) r->Next; + + ExFreePoolWithTag(tmp, _POOL_TAG); + } + } + + if (pSecPolicy->Name) + { + ExFreePoolWithTag(pSecPolicy->Name, _POOL_TAG); + pSecPolicy->Name = NULL; + } + + pSecPolicy->Initialized = FALSE; + + RtlZeroMemory(pSecPolicy->RuleList, sizeof(pSecPolicy->RuleList)); + + + KeReleaseSpinLock(&pSecPolicy->SpinLock, irql); +} + + + +/* + * LoadSecurityPolicy() + * + * Description: + * Parses and loads a security policy. + * + * Parameters: + * pSecPolicy - pointer to a security policy to initialize. + * PolicyFile - string containing the policy filename to parse + * FilePath - string containing full program path of the file we are loading policy for + * + * Returns: + * TRUE if security policy was successfully parsed and loaded, FALSE otherwise. + */ + +BOOLEAN +LoadSecurityPolicy(OUT PSECURITY_POLICY pSecPolicy, IN PWSTR PolicyFile, IN PWSTR FilePath) +{ + OBJECT_ATTRIBUTES oa; + HANDLE hFile = 0; + UNICODE_STRING usPolicyFile; + ULONG size; + NTSTATUS status; + IO_STATUS_BLOCK isb; + CHAR *p, buffer[POLICY_MAX_RULE_LENGTH]; + INT64 offset; + BOOLEAN ret = TRUE, Critical = FALSE; + + + if (pSecPolicy == NULL || PolicyFile == NULL) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("LoadSecurityPolicy(%x, %x, %x): NULL parameter\n", pSecPolicy, PolicyFile, FilePath)); + return FALSE; + } + + + pSecPolicy->Initialized = FALSE; + pSecPolicy->DefaultPolicyAction = ACTION_NONE; + pSecPolicy->ProtectionFlags = BootingUp ? PROTECTION_ALL_OFF : PROTECTION_ALL_ON; + + + RtlInitUnicodeString(&usPolicyFile, PolicyFile); + + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("LoadSecurityPolicy: Parsing %S\n", usPolicyFile.Buffer)); + + + InitializeObjectAttributes(&oa, &usPolicyFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + if (!NT_SUCCESS(ZwCreateFile(&hFile, GENERIC_READ, &oa, &isb, + NULL, 0, FILE_SHARE_READ, FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0))) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("LoadSecurityPolicy: Failed to open file %S\n", usPolicyFile.Buffer)); + return FALSE; + } + + + offset = 0; + buffer[0] = 0; + + + /* only one thread at a time can use LoadPolicyMutex due to use of global variables (PolicyLineNumber, buffer) */ + KeWaitForMutexObject(&LoadPolicyMutex, Executive, KernelMode, FALSE, NULL); + + + gPolicyLineNumber = 1; + gPolicyFilename = usPolicyFile.Buffer; + gFilePath = FilePath; + + while (1) + { + status = ZwReadFile(hFile, NULL, NULL, NULL, &isb, (PVOID) buffer, sizeof(buffer) - 1, + (PLARGE_INTEGER) &offset, NULL); + + if (! NT_SUCCESS(status)) + { + if (status != STATUS_END_OF_FILE) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("LoadSecurityPolicy: ZwReadFile failed rc=%x\n", status)); + ret = FALSE; + PolicyDelete(pSecPolicy); + } + + break; + } + + if (isb.Information == 0) + break; + + buffer[isb.Information] = '\0'; + + /* + * strchr() will return NULL when the line we read exceeds the size of the buffer or + * the last line was not '\n' terminated + */ + + if ((p = strchr(buffer, '\n')) == NULL) + { + /* don't try to parse very long lines */ + + if (isb.Information == sizeof(buffer) - 1) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("LoadSecurityPolicy(%s:%d): Rule is too long\n", gPolicyFilename, gPolicyLineNumber)); + + PolicyDelete(pSecPolicy); + + ret = FALSE; + break; + } + + + /* the last rule was not '\n' terminated */ + + if (PolicyParseRule(pSecPolicy, buffer, &Critical) == FALSE) + { + if (Critical == TRUE) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_DEBUG, ("LoadSecurityPolicy(%S:%d): Encountered a critical error. Aborting.\n", gPolicyFilename, gPolicyLineNumber)); + + PolicyDelete(pSecPolicy); + + ret = FALSE; + break; + } + } + + ret = TRUE; + + break; + } + + *p = 0; + + if (p != buffer && *(p - 1) == '\r') + *(p - 1) = 0; + + + if (PolicyParseRule(pSecPolicy, buffer, &Critical) == FALSE) + { + if (Critical == TRUE) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_DEBUG, ("LoadSecurityPolicy(%S:%d): Encountered a critical error. Aborting.\n", gPolicyFilename, gPolicyLineNumber)); + + PolicyDelete(pSecPolicy); + + ret = FALSE; + break; + } + } + + + offset += p - buffer + 1; + + + if (++gPolicyLineNumber > 10000) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("LoadSecurityPolicy: Policy '%S' is too long. Maximum number of lines is 10000.\n", gPolicyFilename)); + + PolicyDelete(pSecPolicy); + + ret = FALSE; + break; + } + } + + ZwClose(hFile); + + + if (ret != FALSE) + { + pSecPolicy->Initialized = TRUE; + + if (pSecPolicy->DefaultPolicyAction == ACTION_NONE) + pSecPolicy->DefaultPolicyAction = DEFAULT_POLICY_ACTION; + } + + + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("LoadSecurityPolicy: Done Parsing %S. Total number of lines %d. (ret=%d)\n", usPolicyFile.Buffer, gPolicyLineNumber, ret)); + + + KeReleaseMutex(&LoadPolicyMutex, FALSE); + + + return ret; +} + + + +/* + * FindAndLoadSecurityPolicy() + * + * Description: + * Finds and loads a security policy associated with a specified executable filename. + * + * Parameters: + * pSecPolicy - pointer to a security policy to initialize. + * FilePath - string specifying the complete path to an executable + * UserName - optional username, if specified check for a policy in "username" directory first + * + * Returns: + * TRUE if security policy was successfully parsed and loaded, FALSE otherwise. + */ + +BOOLEAN +FindAndLoadSecurityPolicy(OUT PSECURITY_POLICY pSecPolicy, IN PWSTR FilePath, IN PWSTR UserName) +{ + PWSTR filename; + WCHAR PolicyPath[MAX_PATH]; + BOOLEAN ret; + int len; + + + if (pSecPolicy == NULL || FilePath == NULL) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("FindAndLoadSecurityPolicy: NULL argument %x %x\n", pSecPolicy, FilePath)); + return FALSE; + } + + + if (KeGetCurrentIrql() != 0) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("FindAndLoadSecurityPolicy(): irql=%d\n", KeGetCurrentIrql())); + return FALSE; + } + + + filename = wcsrchr(FilePath, L'\\'); + + if (filename == NULL) + filename = FilePath; + else + ++filename; + + + /* if user policy load fails, we loop here again to load the global policy */ +ReloadPolicy: + + if (UserName != NULL) + _snwprintf(PolicyPath, MAX_PATH, L"\\??\\%s\\policy\\%s\\%s.policy", OzoneInstallPath, UserName, filename); + else + _snwprintf(PolicyPath, MAX_PATH, L"\\??\\%s\\policy\\%s.policy", OzoneInstallPath, filename); + + PolicyPath[MAX_PATH - 1] = 0; + + + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("FindAndLoadSecurityPolicy: Loading policy for %S (%S)\n", PolicyPath, FilePath)); + + + ret = LoadSecurityPolicy(pSecPolicy, PolicyPath, FilePath); + if (ret == FALSE) + { + /* If we can't find a policy specific to a user, try to load a global policy instead */ + if (UserName != NULL) + { + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("FindAndLoadSecurityPolicy: Cannot find '%S' policy for user '%S'. Looking for a global policy..\n", filename, UserName)); + + UserName = NULL; + + goto ReloadPolicy; + } + + return FALSE; + } + + + /* allocate extra space for ".policy" string */ + len = wcslen(filename) + 7 + 1; + + pSecPolicy->Name = ExAllocatePoolWithTag(NonPagedPool, len * sizeof(WCHAR), _POOL_TAG); + + if (pSecPolicy->Name != NULL) + { + _snwprintf(pSecPolicy->Name, len, L"%s.policy", filename); + } + else + { + PolicyDelete(pSecPolicy); + ret = FALSE; + } + + return ret; +} + + + +/* + * PolicyParseRule() + * + * Description: + * Parses a specified rule. + * + * Parameters: + * pSecPolicy - pointer to a security policy that will contain the parsed rule. + * rule - string buffer containing a rule to parse. + * Critical - Boolean indicating whether the parser should abort parsing the policy due to a critical error. + * + * Returns: + * TRUE if the policy rule was successfully parsed and loaded, FALSE otherwise. + */ + +#define SKIP_WHITESPACE(str) do { while(*(str) == ' ' || *(str) == '\t') ++(str); } while(0) +#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t') + +/* macro shortcut for bailing out of PolicyParseRule() in case of an error */ + +#define ABORT_PolicyParseRule(msg) \ + do { \ + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Encountered an error while parsing %S:%d :\n", gPolicyFilename, gPolicyLineNumber)); \ + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, msg); \ + return FALSE; \ + } while (0) + + +static BOOLEAN +PolicyParseRule(OUT PSECURITY_POLICY pSecPolicy, IN PCHAR rule, OUT BOOLEAN *Critical) +{ + CHAR ServiceName[POLICY_MAX_SERVICE_NAME_LENGTH]; + PCHAR OriginalRule = rule; + int i = 0, SawSpace = 0, SawUnderscore = 0; + + + *Critical = FALSE; + + + SKIP_WHITESPACE(rule); + + + /* skip empty lines */ + + if (*rule == 0) + return TRUE; + + + /* comments start with '#' */ + + if (*rule == '#') + return TRUE; + + + /* + * Parse the service name. Format: "ServiceName:" or "ServiceName_OperationType:" + * where ServiceName can be "file", "registry", "event", "memory", etc + * and OperationType can be "read", "write", "rw" + */ + + while (*rule != 0 && *rule != ':') + { + /* is the specified syscall name too long? */ + + if (i > POLICY_MAX_SERVICE_NAME_LENGTH - 1) + ABORT_PolicyParseRule(("Rule type specification is too long. Maximum rule type specification length is %d characters.\n", POLICY_MAX_SERVICE_NAME_LENGTH)); + + + /* allow whitespace before the colon */ + + if (IS_WHITESPACE(*rule)) + { + ++rule; + + SawSpace = 1; + + continue; + } + + + /* Service Names are not allowed to contain a space */ + + if (SawSpace) + ABORT_PolicyParseRule(("Rule type specification cannot contain a space\n")); + + + /* Expecting to see 1 underscore '_' */ + + if (*rule == '_') + { + /* There can be only be 1 underscore char. and it cannot be the first char. */ + if (i == 0 || SawUnderscore) + ABORT_PolicyParseRule(("Rule type specification cannot contain multiple underscore characters\n")); + + /* remember the underscore position */ + SawUnderscore = i; + } + + + ServiceName[i++] = *rule++; + } + + + /* Expecting to have read more than 1 character, finishing with a ':' */ + if (i == 0 || *rule++ != ':') + ABORT_PolicyParseRule(("Colon not found. Rule type specification must end with a colon ':'.\n")); + + + ServiceName[i] = 0; + + + SKIP_WHITESPACE(rule); + + + /* didn't see any underscores. assume "system_call_name:" format */ + + if (SawUnderscore == 0) + ABORT_PolicyParseRule(("Underscore not found. Rule type specification must contain an underscore.\n")); + + + ServiceName[SawUnderscore] = 0; + + + // file operation + if (strlen(ServiceName) == 4 && _stricmp(ServiceName, "file") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_FILE, ServiceName + SawUnderscore + 1, rule); + + // directory operation + if (strlen(ServiceName) == 9 && _stricmp(ServiceName, "directory") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_DIRECTORY, ServiceName + SawUnderscore + 1, rule); + + // registry operation + if (strlen(ServiceName) == 8 && _stricmp(ServiceName, "registry") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_REGISTRY, ServiceName + SawUnderscore + 1, rule); + + // memory section operation + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "section") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_SECTION, ServiceName + SawUnderscore + 1, rule); + + // memory section / dll operation + if (strlen(ServiceName) == 3 && _stricmp(ServiceName, "dll") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_DLL, ServiceName + SawUnderscore + 1, rule); + + // event operation + if (strlen(ServiceName) == 5 && _stricmp(ServiceName, "event") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_EVENT, ServiceName + SawUnderscore + 1, rule); + + // semaphore operation + if (strlen(ServiceName) == 9 && _stricmp(ServiceName, "semaphore") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_SEMAPHORE, ServiceName + SawUnderscore + 1, rule); + + // mailslot operation + if (strlen(ServiceName) == 8 && _stricmp(ServiceName, "mailslot") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_MAILSLOT, ServiceName + SawUnderscore + 1, rule); + + // named pipe operation + if (strlen(ServiceName) == 9 && _stricmp(ServiceName, "namedpipe") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_NAMEDPIPE, ServiceName + SawUnderscore + 1, rule); + + // job object operation + if (strlen(ServiceName) == 3 && _stricmp(ServiceName, "job") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_JOB, ServiceName + SawUnderscore + 1, rule); + + // mutant operation + if (strlen(ServiceName) == 5 && _stricmp(ServiceName, "mutex") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_MUTANT, ServiceName + SawUnderscore + 1, rule); + + // port operation + if (strlen(ServiceName) == 4 && _stricmp(ServiceName, "port") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_PORT, ServiceName + SawUnderscore + 1, rule); + + // symlink operation + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "symlink") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_SYMLINK, ServiceName + SawUnderscore + 1, rule); + + // timer operation + if (strlen(ServiceName) == 5 && _stricmp(ServiceName, "timer") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_TIMER, ServiceName + SawUnderscore + 1, rule); + + // process operation + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "process") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_PROCESS, ServiceName + SawUnderscore + 1, rule); + + // driver operation + if (strlen(ServiceName) == 6 && _stricmp(ServiceName, "driver") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_DRIVER, ServiceName + SawUnderscore + 1, rule); + + // object directory operation + if (strlen(ServiceName) == 6 && _stricmp(ServiceName, "dirobj") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_DIROBJ, ServiceName + SawUnderscore + 1, rule); + + // atom operation + if (strlen(ServiceName) == 4 && _stricmp(ServiceName, "atom") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_ATOM, ServiceName + SawUnderscore + 1, rule); + + // network operation + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "network") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_NETWORK, ServiceName + SawUnderscore + 1, rule); + + // service operation + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "service") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_SERVICE, ServiceName + SawUnderscore + 1, rule); + + // time operation + if (strlen(ServiceName) == 4 && _stricmp(ServiceName, "time") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_TIME, ServiceName + SawUnderscore + 1, rule); + + // token operation + if (strlen(ServiceName) == 5 && _stricmp(ServiceName, "token") == 0) + return PolicyParseObjectRule(pSecPolicy, RULE_TOKEN, ServiceName + SawUnderscore + 1, rule); + + + /* + * non object rules + */ + + // syscall + if (strlen(ServiceName) == 7 && _stricmp(ServiceName, "syscall") == 0) + return PolicyParseSyscallRule(pSecPolicy, ServiceName + SawUnderscore + 1, rule); + + // policy + if (strlen(ServiceName) == 6 && _stricmp(ServiceName, "policy") == 0) + return PolicyParsePolicyRule(pSecPolicy, ServiceName + SawUnderscore + 1, rule, Critical); + + // protection + if (strlen(ServiceName) == 10 && _stricmp(ServiceName, "protection") == 0) + return PolicyParseProtectionRule(pSecPolicy, ServiceName + SawUnderscore + 1, rule); + + // media + if (strlen(ServiceName) == 5 && _stricmp(ServiceName, "media") == 0) + return PolicyParseMediaRule(pSecPolicy, ServiceName + SawUnderscore + 1, rule); + + + + ABORT_PolicyParseRule(("Invalid rule type specification: '%s'.\n", ServiceName)); +} + + + +/* + * ParseDllOperation() + * + * Description: + * Parses an operation (i.e. "load" in "dll_load") specified for a DLL object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseDllOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 4 && _stricmp(Operation, "load") == 0) + return OP_LOAD; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + return OP_INVALID; +} + + + +/* + * ParseTimeOperation() + * + * Description: + * Parses an operation (i.e. "change" in "time_change") specified for a Time object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseTimeOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 6 && _stricmp(Operation, "change") == 0) + return OP_LOAD; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + return OP_INVALID; +} + + + +/* + * ParseTokenOperation() + * + * Description: + * Parses an operation (i.e. "modify" in "token_modify") specified for a Token object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseTokenOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 6 && _stricmp(Operation, "modify") == 0) + return OP_TOKEN_MODIFY; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + return OP_INVALID; +} + + + +/* + * ParsePortOperation() + * + * Description: + * Parses an operation (i.e. "create" in "port_create") specified for a port object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParsePortOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 6 && _stricmp(Operation, "create") == 0) + return OP_PORT_CREATE; + + if (strlen(Operation) == 7 && _stricmp(Operation, "connect") == 0) + return OP_PORT_CONNECT; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + + return OP_INVALID; +} + + + +/* + * ParseCreateOpenOperation() + * + * Description: + * Parses a create or an open operation (i.e. "create" in "dirobj_create"). + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseCreateOpenOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 6 && _stricmp(Operation, "create") == 0) + return OP_CREATE; + + if (strlen(Operation) == 4 && _stricmp(Operation, "open") == 0) + return OP_OPEN; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + + return OP_INVALID; +} + + + +/* + * ParseAtomOperation() + * + * Description: + * Parses an operation (i.e. "find" in "atom_find") specified for an atom object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseAtomOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 4 && _stricmp(Operation, "find") == 0) + return OP_FIND; + + if (strlen(Operation) == 3 && _stricmp(Operation, "add") == 0) + return OP_ADD; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + + return OP_INVALID; +} + + + +/* + * ParseDriverOperation() + * + * Description: + * Parses an operation (i.e. "load" in "driver_load") specified for a driver object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseDriverOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 4 && _stricmp(Operation, "load") == 0) + return OP_LOAD; + + if (strlen(Operation) == 7 && _stricmp(Operation, "regload") == 0) + return OP_REGLOAD; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + return OP_INVALID; +} + + + +/* + * ParseDirectoryOperation() + * + * Description: + * Parses an operation (i.e. "create" in "directory_create") specified for a directory object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseDirectoryOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 6 && _stricmp(Operation, "create") == 0) + return OP_DIR_CREATE; + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + return OP_INVALID; +} + + + +/* + * ParseObjectOperation() + * + * Description: + * Parses an operation (i.e. "read" in "file_read") specified for an object (file, registry, etc) rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseObjectOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 4 && _stricmp(Operation, "read") == 0) + return OP_READ; + + if (strlen(Operation) == 5 && _stricmp(Operation, "write") == 0) + return OP_WRITE; + + if (strlen(Operation) == 2 && _stricmp(Operation, "rw") == 0) + return (OP_READ | OP_WRITE); + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + //XXX valid for files only +// if (strlen(Operation) == 6 && _stricmp(Operation, "append") == 0) +// return OP_APPEND; + + if (strlen(Operation) == 7 && _stricmp(Operation, "execute") == 0) + return OP_EXECUTE; + + if (strlen(Operation) == 6 && _stricmp(Operation, "delete") == 0) + return OP_DELETE; + + + return OP_INVALID; +} + + + +/* + * ParseProcessOperation() + * + * Description: + * Parses an operation (i.e. "execute" in "process_execute") specified for a process rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseProcessOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + if (strlen(Operation) == 7 && _stricmp(Operation, "execute") == 0) + return OP_PROC_EXECUTE; + + if (strlen(Operation) == 4 && _stricmp(Operation, "open") == 0) + return OP_PROC_OPEN; + + + return OP_INVALID; +} + + + +/* + * ParseServiceOperation() + * + * Description: + * Parses an operation (i.e. "start" in "service_start") specified for a service object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseServiceOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + /* + * We cannot distinguish between various service operations since service rules are actually + * enforced by the registry rules. Thus convert all operations to OP_ALL for now. + */ + + if (strlen(Operation) == 5 && _stricmp(Operation, "start") == 0) + return OP_ALL;//OP_SERVICE_START; + + if (strlen(Operation) == 4 && _stricmp(Operation, "stop") == 0) + return OP_ALL;//OP_SERVICE_STOP; + + if (strlen(Operation) == 6 && _stricmp(Operation, "create") == 0) + return OP_ALL;//OP_SERVICE_CREATE; + + if (strlen(Operation) == 6 && _stricmp(Operation, "delete") == 0) + return OP_ALL;//OP_SERVICE_DELETE; + + return OP_INVALID; +} + + + +/* + * ParseNetworkOperation() + * + * Description: + * Parses an operation (i.e. "bind" in "network_bind") specified for a network object rule. + * + * Parameters: + * Operation - specified operation. + * + * Returns: + * OP_INVALID if a specified operation is invalid or an OP_* value corresponding to the parsed operation. + */ + +UCHAR +ParseNetworkOperation(IN PCHAR Operation) +{ + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + return OP_ALL; + + if (strlen(Operation) == 10 && _stricmp(Operation, "tcpconnect") == 0) + return OP_TCPCONNECT; + + if (strlen(Operation) == 10 && _stricmp(Operation, "udpconnect") == 0) + return OP_UDPCONNECT; + + if (strlen(Operation) == 7 && _stricmp(Operation, "connect") == 0) + return OP_CONNECT; + + if (strlen(Operation) == 4 && _stricmp(Operation, "bind") == 0) + return OP_BIND; + + return OP_INVALID; +} + + + + +/*****************************************************************************/ + + + + +/* + * ParseNetworkObject() + * + * Description: + * Parses the specified network address (i.e. "127.0.0.1:443"). + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * INVALID_OBJECT_SIZE if the specified network address is invalid. 0 to indicate SUCCESS. + * Network addresses do not require any additional memory to be allocated thus the returned size is 0. + */ + +size_t +ParseNetworkObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR colon; + + + //XXX + // for now connect format is "ipaddr" while bind format is "ipaddr:port" + + colon = strchr(name, ':'); + + if (colon) + { + *Object = colon + 1; +// if ((*Object = (PVOID) atoi(colon + 1)) == 0) +// return INVALID_OBJECT_SIZE; + } + else + { + *Object = name; +// if ((*Object = (PVOID) inet_addr(name)) == 0) +// return INVALID_OBJECT_SIZE; + } + + + return strlen(*Object); +} + + + +/* + * ParseStub() + * + * Description: + * Parse stub for strings that do no require any further parsing. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseStub(IN PCHAR name, OUT PCHAR *ObjectName, OUT BOOLEAN *wildcard) +{ + *ObjectName = name; + + return strlen(name); +} + + + +/* + * ParseRegistryObject() + * + * Description: + * Convert user land registry object names into their kernel land equivalents. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseRegistryObject(IN PCHAR name, OUT PCHAR *ObjectName, OUT BOOLEAN *wildcard) +{ + PCHAR key; + static CHAR buffer[MAX_PATH] = { 0 }; + + + if (_strnicmp(name, "HKEY_LOCAL_MACHINE\\", 19) == 0) + { + /* replace HKEY_LOCAL_MACHINE\ with kernel equivalent of \REGISTRY\MACHINE\ */ + + strcpy(name + 1, "\\REGISTRY\\MACHINE"); + name[18] = '\\'; + + key = name + 1; + } + else if (_strnicmp(name, "HKEY_USERS\\", 11) == 0) + { + /* replace HKEY_USERS\ with kernel equivalent of \REGISTRY\USER\ */ + + strcpy(buffer, "\\REGISTRY\\USER\\"); + strncat(buffer, name + 11, MAX_PATH - 12); + + key = buffer; + } + else + { + key = name; + } + + + *ObjectName = key; + + + return strlen(key); +} + + + +/* + * ParseFileObject() + * + * Description: + * Convert user land file object names into their kernel land equivalents. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseFileObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR filename; + static CHAR buffer[MAX_PATH] = { 0 }; //XXX not SMP safe + + + if (_strnicmp(name, "%systemdrive%:", 14) == 0) + { + name[12] = SystemDrive; + + name += 12; + } + + + // match drive wildcards such as "?:" and "*:" with "\Device\*" + if (name[1] == ':' && name[2] == '\\' && (name[0] == '?' || name[0] == '*')) + { +#if 0 + ConvertLongFileNameToShort(name, buffer + 7, MAX_PATH - 7); + + strcpy(buffer, "\\Device\\*"); + buffer[9] = '\\'; /* replace the zero terminator */ + + filename = buffer; +#endif + + strcpy(name - 7, "\\Device\\*"); + + /* + * replace "\Device\*" terminating zero with a '\' + * since name is just a pointer to FullName+7, FullName now contains + * \Device\*\ + */ + + name[2] = '\\'; + + filename = name - 7; + + + // mark the rule as wildcard even if the user (mistakenly) used "eq" + // XXX or should we throw an error if wildcard==0? + *wildcard = TRUE; + } + else if (isalpha(name[0]) && name[1] == ':') + { +#if 0 + CHAR buffer2[MAX_PATH]; + + + ConvertLongFileNameToShort(name, buffer2 + 4, MAX_PATH - 4); + + buffer2[0] = '\\'; + buffer2[1] = '?'; + buffer2[2] = '?'; + buffer2[3] = '\\'; + + if (ResolveFilename(buffer2, buffer, MAX_PATH) == FALSE) + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("ParseFileObject: ResolveFilename(%s) failed\n", name - 4)); +#endif + + + // match : drive specifications and prepend "\??\" to them + + *(name - 4) = '\\'; + *(name - 3) = '?'; + *(name - 2) = '?'; + *(name - 1) = '\\'; + + if (ResolveFilename(name - 4, buffer, MAX_PATH) == FALSE) + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("ParseFileObject: ResolveFilename(%s) failed\n", name - 4)); + + filename = buffer; + } + else if (_strnicmp(name, "%systemroot%\\", 13) == 0) + { + strcpy(buffer, SystemRoot); + strcat(buffer, name + 12); + + filename = buffer; + } + else if (_strnicmp(name, "\\pipe\\", 6) == 0) + { + strcpy(buffer, "\\device\\namedpipe"); + strcat(buffer, name + 5); + + filename = buffer; + } + else + { + filename = name; + } + + + *Object = filename; + + return strlen(filename); +} + + + +/* + * ParseProcessObject() + * + * Description: + * Convert user land process object names into their kernel land equivalents (strip the drive specification). + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseProcessObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + static CHAR buffer[MAX_PATH] = { 0 }; + + + if ((name = StripFileMacros(name, buffer, MAX_PATH)) == NULL) + return INVALID_OBJECT_SIZE; + + + *Object = name; + + return strlen(name); +} + + + +/* + * ParseBaseNamedObjectsObject() + * + * Description: + * Convert user land object names into their kernel land equivalents. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseBaseNamedObjectsObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR ObjectName; + + + /* + * if an object name does not start with a slash '\' then prepend '\BaseNamedObjects\' to it + */ + + if (name[0] != '\\') + { + //XXX this is a hack, we are prepending to our buffer, knowing that there is space there + strcpy(name - 18, "\\BaseNamedObjects"); + + *(name - 1) = '\\'; + + ObjectName = name - 18; + } + else + { + ObjectName = name; + } + + + *Object = ObjectName; + + return strlen(ObjectName); +} + + + +/* + * ParseMailslotObject() + * + * Description: + * Convert user land mailslot object names into their kernel land equivalents. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseMailslotObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR MailslotName; + + + /* + * if the mailslot name does not start with a slash '\' then prepend '\Device\Mailslot\' to the name + */ + + if (name[0] != '\\') + { + //XXX this is a hack, we are prepending to our buffer, knowing that there is space there + strcpy(name - 17, "\\Device\\Mailslot"); + + *(name - 1) = '\\'; + + MailslotName = name - 17; + } + else + { + MailslotName = name; + } + + + *Object = MailslotName; + + return strlen(MailslotName); +} + + + +/* + * ParseNamedpipeObject() + * + * Description: + * Convert user land namedpipe object names into their kernel land equivalents. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseNamedpipeObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR NamedpipeName; + + + /* + * if the namedpipe name does not start with a slash '\' then prepend '\Device\Namedpipe\' to the name + */ + + if (name[0] != '\\') + { + //XXX this is a hack, we are prepending to our buffer, knowing that there is space there + strcpy(name - 18, "\\Device\\Namedpipe"); + + *(name - 1) = '\\'; + + NamedpipeName = name - 18; + } + else + { + NamedpipeName = name; + } + + + *Object = NamedpipeName; + + return strlen(NamedpipeName); +} + + + +/* + * ParseDllObject() + * + * Description: + * Convert user land DLL object names into their kernel land equivalents. + * Since DLL rules are actually enforced by section rules, we just append DLL names to + * '\KnownDlls\' string which is used by section rules. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + */ + +size_t +ParseDllObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + PCHAR DllName; + + + /* + * if the DLL name does not start with a slash '\' then prepend '\KnownDlls\' to the name + */ + + if (name[0] != '\\') + { + strcpy(name - 11, "\\KnownDlls"); + + *(name - 1) = '\\'; + + DllName = name - 11; + } + else + { + DllName = name; + } + + + *Object = DllName; + + return strlen(DllName); +} + + + +/* + * ParseTimeObject() + * + * Description: + * Time rule specifications are not supposed to have any object names specified. + * Return an error. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * An error. + */ + +size_t +ParseTimeObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + return INVALID_OBJECT_SIZE; +} + + + +/* + * ParseServiceObject() + * + * Description: + * Convert user land service object names into their kernel land equivalents. + * Since service rules are actually enforced by registry rules, we just append service names + * to '\Registry\Machine\System\*ControlSet*\Services\' string which is used by registry rules. + * + * Parameters: + * name - string value to parse. + * Object - pointer to an Object where the final result will be saved. + * wildcard - pointer to a BOOLEAN that will indicate whether the specified value contained any regular expressions. + * + * Returns: + * Length of the specified string value. + * INVALID_OBJECT_SIZE is service name is too long. + */ + +size_t +ParseServiceObject(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard) +{ + static CHAR buffer[MAX_PATH] = { 0 }; //XXX not SMP safe + + + if (strlen(name) > 64) + { + return INVALID_OBJECT_SIZE; + } + + strcpy(buffer, "\\Registry\\Machine\\System\\*ControlSet*\\Services\\"); + strcat(buffer, name); + + *wildcard = TRUE; + + *Object = buffer; + + + return strlen(buffer); +} + + + +typedef size_t (*OBJECT_PARSER)(IN PCHAR name, OUT PCHAR *Object, OUT BOOLEAN *wildcard); +typedef UCHAR (*OPERATION_TYPE_PARSER)(IN PCHAR name); + + +/* in C++ these would be member methods */ + +struct _ObjectParseOps +{ + RULE_TYPE RuleType; + OBJECT_PARSER ObjectNameParser; + OPERATION_TYPE_PARSER OperationTypeParser; + +} ObjectParseOps[] = +{ + { RULE_FILE, ParseFileObject, ParseObjectOperation }, + { RULE_DIRECTORY, ParseFileObject, ParseDirectoryOperation }, + { RULE_MAILSLOT, ParseMailslotObject, ParseObjectOperation }, + { RULE_NAMEDPIPE, ParseNamedpipeObject, ParseObjectOperation }, + { RULE_REGISTRY, ParseRegistryObject, ParseObjectOperation }, + { RULE_SECTION, ParseBaseNamedObjectsObject, ParseObjectOperation }, + { RULE_DLL, ParseDllObject, ParseDllOperation }, + { RULE_EVENT, ParseBaseNamedObjectsObject, ParseCreateOpenOperation }, + { RULE_SEMAPHORE, ParseBaseNamedObjectsObject, ParseCreateOpenOperation }, + { RULE_JOB, ParseBaseNamedObjectsObject, ParseCreateOpenOperation }, + { RULE_MUTANT, ParseBaseNamedObjectsObject, ParseCreateOpenOperation }, + { RULE_PORT, ParseStub, ParsePortOperation }, + { RULE_SYMLINK, ParseStub, ParseCreateOpenOperation }, + { RULE_TIMER, ParseBaseNamedObjectsObject, ParseCreateOpenOperation }, + { RULE_PROCESS, ParseProcessObject, ParseProcessOperation }, + { RULE_DRIVER, ParseProcessObject, ParseDriverOperation }, + { RULE_DIROBJ, ParseStub, ParseCreateOpenOperation }, + { RULE_ATOM, ParseStub, ParseAtomOperation }, + { RULE_NETWORK, ParseNetworkObject, ParseNetworkOperation }, + { RULE_SERVICE, ParseServiceObject, ParseServiceOperation }, + { RULE_TIME, ParseTimeObject, ParseTimeOperation }, + { RULE_TOKEN, ParseTimeObject, ParseTokenOperation }, +}; + + + +/* + * PolicyParseActionClause() + * + * Description: + * . + * + * Parameters: + * . + * + * Returns: + * . + */ + +BOOLEAN +PolicyParseActionClause(PCHAR rule, ACTION_TYPE *ActionType, UCHAR *RuleNumber) +{ + UCHAR len = 0, num = 0; + + + SKIP_WHITESPACE(rule); + + + if (_strnicmp(rule, "permit", 6) == 0) + { + rule += 6; + + *ActionType = ACTION_PERMIT; + } + else if (_strnicmp(rule, "deny", 4) == 0) + { + rule += 4; + + *ActionType = ACTION_DENY; + } + else if (_strnicmp(rule, "quietdeny", 9) == 0) + { + rule += 9; + + *ActionType = ACTION_QUIETDENY; + } + else if (_strnicmp(rule, "log", 3) == 0) + { + rule += 3; + + *ActionType = ACTION_LOG; + } + else if (_strnicmp(rule, "ask", 3) == 0) + { + rule += 3; + + *ActionType = ACTION_ASK; + } + else + { + ABORT_PolicyParseRule(("Expecting to see 'permit', 'deny', 'quitedeny', 'log' or 'ask' clause. Got '%s'\n", rule)); + } + + + SKIP_WHITESPACE(rule); + + + /* EOL? */ + if (*rule == 0) + { + if (RuleNumber) + *RuleNumber = 0; + + return TRUE; + } + + + /* if it is not EOL then we expect to see "[rule DIGIT]" clause */ + if (_strnicmp(rule, "[rule", 5) == 0) + { + rule += 5; + } + else + { + ABORT_PolicyParseRule(("Expecting to see a rule clause. Got '%s'\n", rule)); + } + + + if (! IS_WHITESPACE(*rule)) + { + ABORT_PolicyParseRule(("Expecting to see white space. Got '%s'\n", rule)); + } + + SKIP_WHITESPACE(rule); + + + /* parse the rule number (a decimal digit) */ + while (*rule >= '0' && *rule <= '9') + { + /* don't overflow UCHAR values */ + if (++len > 2) + { + ABORT_PolicyParseRule(("The rule number is too long.\n")); + } + + num = num*10 + (*rule - '0'); + + ++rule; + } + + + SKIP_WHITESPACE(rule); + + + if (*rule != ']') + { + ABORT_PolicyParseRule(("Invalid rule clause: '%s'\n", rule)); + } + + + ++rule; + SKIP_WHITESPACE(rule); + + + /* expecting an EOL */ + if (*rule != 0) + { + ABORT_PolicyParseRule(("Expecting to see end of line. Got '%s'\n", rule)); + } + + + if (RuleNumber) + *RuleNumber = num; + + + return TRUE; +} + + + +/* + * PolicyParseOnOffClause() + * + * Description: + * . + * + * Parameters: + * . + * + * Returns: + * . + */ + +BOOLEAN +PolicyParseOnOffClause(PCHAR rule, BOOLEAN *OnOff) +{ + SKIP_WHITESPACE(rule); + + + if (_strnicmp(rule, "on", 2) == 0) + { + rule += 2; + + *OnOff = TRUE; + } + else if (_strnicmp(rule, "off", 3) == 0) + { + rule += 3; + + *OnOff = FALSE; + } + else + { + ABORT_PolicyParseRule(("Expecting to see 'on' or 'off' clause. Got '%s'\n", rule)); + } + + + SKIP_WHITESPACE(rule); + + /* expecting an EOL */ + if (*rule != 0) + { + ABORT_PolicyParseRule(("Expecting to see end of line. Got '%s'\n", rule)); + } + + return TRUE; +} + + + +/* + * VerifyToken2() + * + * Description: + * . + * + * Parameters: + * . + * + * Returns: + * . + */ + +UCHAR +VerifyToken2(PCHAR *rule, PCHAR token1, PCHAR token2) +{ + USHORT Token1Length, Token2Length; + + + ASSERT(rule && *rule); + ASSERT(token1); + ASSERT(token2); + + + SKIP_WHITESPACE(*rule); + + + Token1Length = (USHORT) strlen(token1); + Token2Length = (USHORT) strlen(token2); + + if (_strnicmp(*rule, token1, Token1Length) == 0) + { + *rule += Token1Length; + + if (! IS_WHITESPACE(**rule)) + { +// LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Expecting to see whitespace. Got '%s'\n", *rule)); + return FALSE; + } + + SKIP_WHITESPACE(*rule); + + return 1; + } + + + if (_strnicmp(*rule, token2, Token2Length) == 0) + { + *rule += Token2Length; + + if (! IS_WHITESPACE(**rule)) + { +// LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Expecting to see whitespace. Got '%s'\n", *rule)); + return FALSE; + } + + SKIP_WHITESPACE(*rule); + + return 2; + } + + + return 0; +} + + + +/* + * PolicyParsePolicyRule() + * + * Description: + * Parse a policy rule and adjust the policy default action. + * + * Parameters: + * pSecPolicy - security policy that the rule is going to be added to. + * Operation - ASCII operation (valid options are 'default' and 'path'). + * Rule - ASCII rule to parse. + * Critical - Boolean indicating whether the parser should abort parsing the policy due to a critical error. + * + * Returns: + * Nothing. + */ + +BOOLEAN +PolicyParsePolicyRule(OUT PSECURITY_POLICY pSecPolicy, IN PCHAR Operation, IN PCHAR rule, OUT BOOLEAN *Critical) +{ + ACTION_TYPE ActionType; + UCHAR RuleNumber; + + + *Critical = FALSE; + + + if (strlen(Operation) == 7 && _stricmp(Operation, "default") == 0) + { + if (PolicyParseActionClause(rule, &ActionType, &RuleNumber) == FALSE) + + ABORT_PolicyParseRule(("Invalid default policy action specification. Format: \"policy_default: (permit|deny|quietdeny|log|ask)\"\n")); + + + /* did we initialize default policy action already? */ + if (pSecPolicy->DefaultPolicyAction != ACTION_NONE) + + ABORT_PolicyParseRule(("Duplicate default policy action specification.\n")); + + + /* this still allows "policy_default: permit [rule 0]", oh well */ + if (RuleNumber != 0) + + ABORT_PolicyParseRule(("Rule clause cannot appear in default policy action specification.\n")); + + + pSecPolicy->DefaultPolicyAction = ActionType | ACTION_DEFAULT; + + return TRUE; + } + + + if (strlen(Operation) == 4 && _stricmp(Operation, "path") == 0) + { + CHAR szPath[MAX_PATH]; + CHAR szPolicyPath[MAX_PATH]; + + + _snprintf(szPath, MAX_PATH, "%S", gFilePath); + szPath[MAX_PATH - 1] = 0; + + + rule = StripFileMacros(rule, szPolicyPath, MAX_PATH); + + + KdPrint(("%s\n%s\n", szPath, rule)); + + if (WildcardMatch(szPath, rule) == WILDCARD_MATCH) + { + KdPrint(("paths match\n")); + return TRUE; + } + + + if (LearningMode == FALSE) + *Critical = TRUE; + + KdPrint(("paths do not match\n")); + + + return FALSE; + } + + + ABORT_PolicyParseRule(("Invalid policy operation '%s'. Valid options are 'default' and 'path'.\n", Operation)); +} + + + +/* + * PolicyParseProtectionRule() + * + * Description: + * Parse a protection rule and adjust the protection options such as buffer overflow protection + * and userland (dll injection) protection being on and off + * + * Parameters: + * pSecPolicy - security policy that the rule is going to be added to. + * Operation - ASCII operation. Valid options are 'overflow', 'userland', 'debugging', 'dos16', 'keyboard', 'modem', 'sniffer', 'extension' and 'all'. + * Rule - ASCII rule to parse. + * + * Returns: + * Nothing. + */ + +BOOLEAN +PolicyParseProtectionRule(PSECURITY_POLICY pSecPolicy, PCHAR Operation, PCHAR rule) +{ + BOOLEAN OnOff; + + + if (strlen(Operation) == 8 && _stricmp(Operation, "overflow") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning overflow protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_OVERFLOW; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_OVERFLOW; + + return TRUE; + } + + + if (strlen(Operation) == 8 && _stricmp(Operation, "userland") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning userland protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_USERLAND; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_USERLAND; + + return TRUE; + } + + + if (strlen(Operation) == 9 && _stricmp(Operation, "debugging") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning debugging protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_DEBUGGING; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_DEBUGGING; + + return TRUE; + } + + + if (strlen(Operation) == 5 && _stricmp(Operation, "dos16") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning dos16/vdm protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_VDM; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_VDM; + + return TRUE; + } + + + if (strlen(Operation) == 8 && _stricmp(Operation, "keyboard") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning keyboard logger protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_KEYBOARD; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_KEYBOARD; + + return TRUE; + } + + + if (strlen(Operation) == 5 && _stricmp(Operation, "modem") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning modem protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_MODEM; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_MODEM; + + return TRUE; + } + + + if (strlen(Operation) == 7 && _stricmp(Operation, "sniffer") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning sniffer protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_SNIFFER; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_SNIFFER; + + return TRUE; + } + + + if (strlen(Operation) == 9 && _stricmp(Operation, "extension") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning extension protection %s\n", OnOff ? "on" : "off")); + + if (OnOff) + pSecPolicy->ProtectionFlags |= PROTECTION_EXTENSION; + else + pSecPolicy->ProtectionFlags &= ~PROTECTION_EXTENSION; + + return TRUE; + } + + + if (strlen(Operation) == 3 && _stricmp(Operation, "all") == 0) + { + if (PolicyParseOnOffClause(rule, &OnOff) == FALSE) + return FALSE; + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseProtectionRule: Turning all protection %s\n", OnOff ? "on" : "off")); + + pSecPolicy->ProtectionFlags = OnOff == TRUE ? PROTECTION_ALL_ON : PROTECTION_ALL_OFF; + + return TRUE; + } + + + ABORT_PolicyParseRule(("Invalid protection operation '%s'. Valid options are 'overflow', 'userland', 'debugging', 'dos16', 'keyboard', 'modem', 'sniffer', 'extension' and 'all'.\n", Operation)); +} + + + +/* + * PolicyParseMediaRule() + * + * Description: + * Parse a media rule. + * + * Parameters: + * pSecPolicy - security policy that the rule is going to be added to. + * Operation - ASCII operation. The only valid option is 'access'. + * Rule - ASCII rule to parse. + * + * Returns: + * Nothing. + */ + +BOOLEAN +PolicyParseMediaRule(PSECURITY_POLICY pSecPolicy, PCHAR Operation, PCHAR rule) +{ + if (pSecPolicy != &gSecPolicy) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("PolicyParseMediaRule: Media rules can be setup only in a global policy\n")); + return TRUE; + } + + + if (strlen(Operation) != 6 || _stricmp(Operation, "access") != 0) + ABORT_PolicyParseRule(("Invalid media operation '%s'. The only valid option is 'access'.\n", Operation)); + + + if (strlen(rule) == 6 && _stricmp(rule, "permit") == 0) + { + MediaRemovableFlags = MEDIA_REMOVABLE_PERMIT; + return TRUE; + } + + if (strlen(rule) == 4 && _stricmp(rule, "deny") == 0) + { + MediaRemovableFlags |= MEDIA_REMOVABLE_DISABLE; + return TRUE; + } + + if (strlen(rule) == 8 && _stricmp(rule, "readonly") == 0) + { + MediaRemovableFlags |= MEDIA_REMOVABLE_READONLY; + return TRUE; + } + + if (strlen(rule) == 9 && _stricmp(rule, "noexecute") == 0) + { + MediaRemovableFlags |= MEDIA_REMOVABLE_NOEXECUTE; + return TRUE; + } + + + ABORT_PolicyParseRule(("Expecting to see 'permit', 'deny', 'readonly', or 'noexecute' action. Got '%s'\n", rule)); +} + + + +/* + * InsertPolicyRule() + * + * Description: + * Adds a rule to a specified security policy (FIFO order). + * + * Parameters: + * pSecPolicy - security policy that the rule is going to be added to. + * PolicyRule - rule to add. + * RuleType - rule type (file, network, etc). + * + * Returns: + * Nothing. + */ + +VOID +InsertPolicyRule(PSECURITY_POLICY pSecPolicy, PPOLICY_RULE PolicyRule, RULE_TYPE RuleType) +{ + KIRQL irql; + PPOLICY_RULE tmp; + + + ASSERT(RuleType < RULE_LASTONE); + + + KeAcquireSpinLock(&pSecPolicy->SpinLock, &irql); + + + if (pSecPolicy->RuleList[RuleType] == NULL) + { + pSecPolicy->RuleList[RuleType] = PolicyRule; + + KeReleaseSpinLock(&pSecPolicy->SpinLock, irql); + + return; + } + + /* find the last rule and link the new rule to it */ + tmp = pSecPolicy->RuleList[RuleType]; + + while (tmp->Next) + { + tmp = tmp->Next; + } + + tmp->Next = PolicyRule; + + + KeReleaseSpinLock(&pSecPolicy->SpinLock, irql); +} + + + +/* + * PolicyParseObjectRule() + * + * Description: + * Parse an object rule of the following format: + * (name|address) (eq|match) "" then (deny|quitedeny|permit|log) + * Rule can also consist of just (deny|quitedeny|permit|log) + * + * example1: name match "c:\file*" then deny + * example2: address eq "192.168.0.1" then log + * example3: quietdeny + * + * Parameters: + * pSecPolicy - security policy that the rule is going to be added to. + * RuleType - rule type (file, network, etc). + * Operation - ASCII operation (read, write, etc). + * Rule - ASCII rule to parse. NOTE: this field gets clobbered. + * + * Returns: + * Nothing. + */ + +BOOLEAN +PolicyParseObjectRule(PSECURITY_POLICY pSecPolicy, RULE_TYPE RuleType, PCHAR Operation, PCHAR rule) +{ + PCHAR name = NULL; + PCHAR OriginalRule = rule; + int i, TotalStars; + BOOLEAN wildcard, WildcardWarning = FALSE; + BOOLEAN ParseLastToken = FALSE; + ACTION_TYPE ActionType; + MATCH_TYPE MatchType; + PPOLICY_RULE PolicyRule; + UCHAR OperationType; + PCHAR ObjectName = NULL; + size_t ObjectSize = 0; + UCHAR RuleNumber; + + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseObjectRule: Parsing rule '%s': '%s'\n", Operation, rule)); + + + /* + * First token can be "name" or "address" for network rules + * Alternatively, the entire rule can consist of an action clause (such as permit, deny, quietdeny or log) + */ + + switch (VerifyToken2(&rule, "name", "address")) + { + /* matched token1 - "name" */ + case 1: break; + + /* matched token2 - "address" */ + case 2: + { + /* only network rules can substitute "address" for "name" */ + + if (RuleType != RULE_NETWORK) + + ABORT_PolicyParseRule(("Expecting to see 'name'. Got 'address'\n")); + + break; + } + + /* didn't match "name" or "address". try to parse as an action clause */ + default: + { + ParseLastToken = TRUE; + goto ParseLastToken; + } + } + + + /* + * Second token should be "eq" or "match" + */ + + switch (VerifyToken2(&rule, "eq", "match")) + { + /* matched token1 - "eq" */ + case 1: wildcard = FALSE; break; + + /* matched token2 - "match" */ + case 2: wildcard = TRUE; break; + + /* didn't match "eq" or "match" */ + default: ABORT_PolicyParseRule(("Expecting to see 'eq' or 'match'. Got '%s'\n", rule)); + } + + + /* + * Third token is the object names in quotes + */ + + /* parse the object name surrounded by quotes: "" */ + + if (*rule++ != '"') + ABORT_PolicyParseRule(("Initial quote character not found. Object names should be surrounded by quotes.\n")); + + + name = rule; + + TotalStars = i = 0; + + + while (*rule != 0 && *rule != '"') + { + if (i >= POLICY_MAX_OBJECT_NAME_LENGTH-1) + ABORT_PolicyParseRule(("Object name '%s' is too long.\nMaximum name length is %d characters.\n", rule - POLICY_MAX_OBJECT_NAME_LENGTH, POLICY_MAX_OBJECT_NAME_LENGTH)); + + // fail bad regexes + if (*rule == '*') + { + if (++TotalStars > POLICY_TOTAL_NUMBER_OF_STARS) + ABORT_PolicyParseRule(("Invalid regular expression. Maximum of %d stars are allowed\n", POLICY_TOTAL_NUMBER_OF_STARS)); + } + + + if (wildcard == FALSE && (*rule == '*' || *rule == '?') && WildcardWarning == FALSE) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("%S:%d:\n", gPolicyFilename, gPolicyLineNumber)); + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Found a regular expression with an 'eq' operator. Use 'match' operator to enable regular expressions.\n")); + WildcardWarning = TRUE; + } + + + ++i; + ++rule; + } + + + if (i == 0 || *rule++ != '"') + ABORT_PolicyParseRule(("Final quote character not found. Object names should be surrounded by quotes.\n")); + + name[i] = 0; + + + if (! IS_WHITESPACE(*rule)) + { + ABORT_PolicyParseRule(("Expecting to see white space. Got '%s'\n", rule)); + } + + SKIP_WHITESPACE(rule); + + + /* + * Fourth token is "then" + */ + + if (VerifyToken2(&rule, "then", "") != 1) + ABORT_PolicyParseRule(("Expecting to see 'then'. Got '%s'\n", rule)); + + /* + * Fifth/Last token is "permit", "deny", "quietdeny", "log" or "ask" + */ + +ParseLastToken: + + if (PolicyParseActionClause(rule, &ActionType, &RuleNumber) == FALSE) + + return FALSE; + + + /* + * Rule parsed ok. Create a new rule. + */ + + if (RuleType > sizeof(ObjectParseOps) / sizeof(ObjectParseOps[0])) + ABORT_PolicyParseRule(("Invalid rule type\n")); + + + if (name) + { + if ((ObjectSize = ObjectParseOps[RuleType].ObjectNameParser(name, &ObjectName, &wildcard)) == INVALID_OBJECT_SIZE) + ABORT_PolicyParseRule(("Invalid object name '%s'\n", name)); + + MatchType = (wildcard == TRUE ? MATCH_WILDCARD : MATCH_SINGLE); + } + else + { + ObjectSize = 0; + MatchType = MATCH_ALL; + } + + + if ((OperationType = ObjectParseOps[RuleType].OperationTypeParser(Operation)) == OP_INVALID) + ABORT_PolicyParseRule(("Invalid operation '%s'\n", Operation)); + + + /* POLICY_RULE already includes 1 character for the name buffer */ + + PolicyRule = ExAllocatePoolWithTag(NonPagedPool, sizeof(POLICY_RULE) + ObjectSize, _POOL_TAG); + if (PolicyRule == NULL) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Object policy parser is out of memory\n")); + return FALSE; + } + + RtlZeroMemory(PolicyRule, sizeof(POLICY_RULE)); + + PolicyRule->ActionType = ActionType; + PolicyRule->MatchType = MatchType; + PolicyRule->OperationType = OperationType; + + PolicyRule->RuleNumber = RuleNumber; + PolicyRule->PolicyLineNumber = gPolicyLineNumber; + PolicyRule->pSecurityPolicy = pSecPolicy; + + + /* ObjectSize can be 0 for MATCH_ALL rules */ + if (ObjectSize) + { + PolicyRule->NameLength = (USHORT) ObjectSize; + strcpy(PolicyRule->Name, (PCHAR) ObjectName); + } + + + /* + * Some rules (such as DLL) are actually enforced by different rules (i.e. section). + * If LearningMode = TRUE then there is no need to convert + */ + + if (LearningMode == FALSE) + { + /* DLL rules are enforced by section rules. */ + if (RuleType == RULE_DLL) + RuleType = RULE_SECTION; + + /* Service rules are enforced by registry rules. */ + if (RuleType == RULE_SERVICE) + RuleType = RULE_REGISTRY; + } + + + InsertPolicyRule(pSecPolicy, PolicyRule, RuleType); + + + return TRUE; +} + + + +/* + * PolicyParseSyscallRule() + * + * Description: + * . + * + * Parameters: + * . + * + * Returns: + * Nothing. + */ + +BOOLEAN +PolicyParseSyscallRule(PSECURITY_POLICY pSecPolicy, PCHAR SyscallName, PCHAR rule) +{ + PPOLICY_RULE PolicyRule; + KIRQL irql; + ACTION_TYPE ActionType; + ULONG SyscallNameIndex; + BOOLEAN AcceptAll = FALSE; + + +#if HOOK_SYSCALLS + + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_VERBOSE, ("PolicyParseSyscallRule: Parsing syscall '%s' rule: '%s'\n", SyscallName, rule)); + + + /* expecting to see "permit", "deny", "quietdeny" or "log" */ + if (PolicyParseActionClause(rule, &ActionType, NULL) == FALSE) + { +// LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_DEBUG, ("PolicyParseSyscallRule: PolicyParseActionClause failed\n")); + return FALSE; + } + +#if 0 + /* + * all the special system calls such as OpenFile and CreateProcess have already been hooked, it should + * be safe to hook everything else. If a special system call is specified then HookSystemServiceByName + * will just silently fail since it's already hooked. + */ + + if (HookSystemServiceByName(SyscallName, NULL/*USE_DEFAULT_HOOK_FUNCTION*/) == FALSE) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Unknown syscall '%s'\n", SyscallName)); + return FALSE; + } +#endif + + + if (strlen(SyscallName) == 3 && _stricmp(SyscallName, "all") == 0) + { + AcceptAll = TRUE; + } + else + { + SyscallNameIndex = FindSystemServiceNumber(SyscallName); + if (SyscallNameIndex == -1) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_DEBUG, ("PolicyParseSyscallRule: Syscall name '%s' not found\n", SyscallName)); + return FALSE; + } + } + + + /* allocate enough memory to hold a bit array for all existing system calls */ + + if (pSecPolicy->RuleList[ RULE_SYSCALL ] == NULL) + { + /* + * Take into account 1 ULONG that is already preallocated in POLICY_RULE. + */ + + USHORT Size = 1 + (USHORT) (ZwCallsNumber - NumberOfBitsInUlong) / 8; + + + PolicyRule = ExAllocatePoolWithTag(NonPagedPool, sizeof(POLICY_RULE) + Size, _POOL_TAG); + if (rule == NULL) + { + LOG(LOG_SS_POLICY_PARSER, LOG_PRIORITY_WARNING, ("Policy parser is out of memory\n")); + return FALSE; + } + + RtlZeroMemory(PolicyRule, sizeof(POLICY_RULE) + Size); + + + /* if default action is permit, then fill the entire bit array with 1's */ + if (pSecPolicy->DefaultPolicyAction == ACTION_PERMIT) + { + RtlFillMemory(&PolicyRule->ServiceBitArray, sizeof(PolicyRule->ServiceBitArray) + Size, 0xFF); + } + + + KeAcquireSpinLock(&pSecPolicy->SpinLock, &irql); + + pSecPolicy->RuleList[ RULE_SYSCALL ] = PolicyRule; + + KeReleaseSpinLock(&pSecPolicy->SpinLock, irql); + } + else + { + PolicyRule = pSecPolicy->RuleList[ RULE_SYSCALL ]; + } + + + // syscall_all: permit + // syscall_all: deny + + // syscall_blah: permit + // syscall_all: deny + + if (AcceptAll == TRUE) + ;//XXX + + { + /* Calculate the bit array ULONG we need to modify (bit array consists of a bunch of ULONGs) */ + ULONG UlongCount = SyscallNameIndex >> UlongBitShift; + + /* Choose the correct bit array ULONG */ + PULONG BitArray = &PolicyRule->ServiceBitArray[0] + UlongCount; + + + //XXX what about log? + if (ActionType == ACTION_PERMIT) + { + /* set the bit */ + *BitArray |= 1 << (SyscallNameIndex - (UlongCount << UlongBitShift)); + } + else if (ActionType >= ACTION_DENY) + { + /* reset the bit */ + *BitArray &= ~( 1 << (SyscallNameIndex - (UlongCount << UlongBitShift)) ); + } + } + +#endif + + + return TRUE; +} + + + +/* + * WildcardMatch() + * + * Description: + * Simple regex algorithm. Supports '?' for a single character match (does not match EOF) and + * '*' for multiple characters (must reside in the same directory). + * + * i.e. c:\temp\*\blah will match c:\temp\temp2\blah but not c:\temp\temp2\temp3\blah + * + * NOTE: WildcardMatch() is at least 10x slower than simple _stricmp(). + * + * Parameters: + * path - ASCII pathname. + * regex - regular expression to match + * + * Returns: + * WILDCARD_MATCH (0) in case of a match, WILDCARD_NO_MATCH (1) otherwise. + */ + +int +WildcardMatch(PCHAR path, PCHAR regex) +{ + BOOLEAN MultipleDirectoryMatch = FALSE, SkippedOverWhitespace = FALSE, ShortFileName = FALSE; + + + if (path == NULL || regex == NULL) + return WILDCARD_NO_MATCH; + + + while (*regex) + { + /* + * SPECIAL CASE. + * Try to deal with short names (longna~1.txt vs long name.txt). + * when we encounter a ~X where X is a digit we skip over it and rewind regex + * to either the next '\' or whatever the next path character is. + * + * The reason for this is when we encounter a long directory it will be abbreviated as follows + * c:\Documents and Settings\... -> c:\DOCUME~1\... + * Thus by matching "DOCUME" and then skipping over ~1 in the pathname and by rewinding regex + * until the next '\' we match the two directories. + * + * The long filenames are matched and abbreviated as follows + * c:\longfilename.txt -> c:\longfi~1.txt + * Thus we match "longfi", skip over ~1 in the pathname and rewind regex until we match + * the next path character which is '.', then we match ".txt" + * + * The following scheme was mostly designed to deal with "c:\documents and settings" and + * "c:\program files" directories since they are two very common long names. It breaks + * in a lot of different cases. Consider the following abbreviation list + * + * LONGNA~1 long name1 + * LONGNA~2 long name2 + * LONGNA~3 long name3 + * LONGNA~4 long name4 + * LOA926~1 long name5 + * LOB926~1 long name6 + * + * When more than four names match to the same short name prefix, Windows switches to an + * alternative hash based schemes which is not handled by our code. + * + * Another restriction (there are probably more) has to do with long extensions which are abbreviated + * as follows: file.html.text -> filete~1.htm. The following code does not handle this case either. + * + * All in all, the following code is an ugly hack that is designed to work for most common cases + * with a least amount of effort. + */ + + if (*path == '~' && isdigit(*(path + 1))) + { + /* rewind regex until we see '\' or whichever character (ie '.' as in ~1.txt) follows ~X in the path */ + while (*regex && *regex != '\\' && *regex != *(path + 2) /*&& *regex != '*' && *regex != '?'*/) + ++regex; + + /* skip over ~X */ + path += 2; + + ShortFileName = TRUE; + } + + + if (*regex == '*') + { + PCHAR str; + + /* + * match one or more characters + */ + + /* if regular expression ends with '*', automatically match the rest of the pathname */ + if (*(regex + 1) == 0) + return WILDCARD_MATCH; + + if (*(regex + 1) == '*') + { + ++regex; + MultipleDirectoryMatch = TRUE; + } + + str = path; + while (*str) + { + if (WildcardMatch(str, regex+1) == WILDCARD_MATCH) + return WILDCARD_MATCH; + + if (MultipleDirectoryMatch == FALSE && *str == '\\') + break; + + ++str; + } + } + else if (*regex == '?') + { + /* + * match one character + */ + + if (*path == 0 || *path == '\\') + return WILDCARD_NO_MATCH; + + ++path; + } + else if (*regex == '\\') + { + /* if we skipped over whitespace but did not match a short name then bail out as not skipping over + whitespace would not get us this far anyway */ + if (SkippedOverWhitespace == TRUE && ShortFileName == FALSE) + return WILDCARD_NO_MATCH; + + ShortFileName = FALSE; + SkippedOverWhitespace = FALSE; + + /* + * match one or more slashes + */ + + if (*path++ != '\\') + return WILDCARD_NO_MATCH; + + while (*path == '\\') + ++path; + } + else + { + /* + * match all other characters + */ + +// if (toupper(*path++) != toupper(*regex)) +// return WILDCARD_NO_MATCH; + + if (toupper(*path) != toupper(*regex)) + { + /* + * Skip over whitespace to match long filenames which are abbreviated with all + * whitespace stripped. In order not to match two filenames with different amount + * of whitespace, we insert an additional check when we reach '\' once we found out + * whether we are working with an abbreviated name. + */ + if (*regex == ' ') + SkippedOverWhitespace = TRUE; + else + return WILDCARD_NO_MATCH; + } + else + { + ++path; + } + } + + ++regex; + } + + + /* make sure pathname is not longer than the regex */ + return *path == 0 ? WILDCARD_MATCH : WILDCARD_NO_MATCH; +} + + + +/* + * PolicyCheckPolicy() + * + * Description: + * . + * + * Parameters: + * . + * + * Returns: + * . + */ + +ACTION_TYPE +PolicyCheckPolicy(PSECURITY_POLICY pSecPolicy, RULE_TYPE RuleType, PCHAR Object, UCHAR OperationType, ACTION_TYPE DefaultAction, UCHAR *RuleNumber, PWSTR *PolicyFilename, USHORT *PolicyLineNumber) +{ + PPOLICY_RULE r; + ACTION_TYPE ret = DefaultAction; + KIRQL irql; + size_t len; + + + ASSERT(pSecPolicy); + ASSERT(PolicyFilename && PolicyLineNumber); + ASSERT(RuleNumber); + + + if (Object) + len = strlen(Object); + + *PolicyFilename = NULL; + *PolicyLineNumber = 0; + *RuleNumber = 0; + + + if (pSecPolicy->Initialized == FALSE) + + return ACTION_NONE; + + + KeAcquireSpinLock(&pSecPolicy->SpinLock, &irql); + + + *PolicyFilename = pSecPolicy->Name; + + + r = pSecPolicy->RuleList[RuleType]; + + while (r) + { +//XXX at least for registry keys there is no point in comparing the first 10 characters since they will always be \Registry\ ? + + if ( (r->MatchType == MATCH_ALL) || + (r->MatchType == MATCH_SINGLE && len == r->NameLength && _stricmp(Object, r->Name) == 0) || + (r->MatchType == MATCH_WILDCARD && WildcardMatch(Object, r->Name) == WILDCARD_MATCH) ) + { + if (Object) + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("%d PolicyCheckPolicy: matched '%s' (%s) (%x %x %x)\n", CURRENT_PROCESS_PID, Object, r->Name, r->MatchType, r->OperationType, OperationType)); + else + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("%d PolicyCheckPolicy: matched RuleType %d MatchType %d OpType %d\n", CURRENT_PROCESS_PID, RuleType, r->MatchType, r->OperationType)); + + +// if (r->OperationType == OP_APPEND) +// { +// ret = ACTION_PROCESS; +// break; +// } + + + /* + * (r->OperationType & OperationType) == r->OperationType + * (r->OperationType & OperationType) + * + * policy_default: deny + * file_read: file.txt + * + * del file.txt (opens file for read + write) + * + * (read & read_write) = read + * + * successfully overwrites the file! + */ + + /* + * (r->OperationType & OperationType) == OperationType + * + * policy_default: permit + * file_write: file.txt deny + * file_read: file.txt + * + * del file.txt (opens file for read + write) + * + * (write & read_write) != read_write + * + * successfully deletes the file! + */ + + // XXX if we only allow reading but both read+execute are requested, the following + // if will match! (bad in deny all scenario!) + if (r->OperationType == OP_ALL || r->OperationType & OperationType) +// if (r->OperationType == OP_ALL || (r->OperationType & OperationType) == r->OperationType) +// if (r->OperationType == OP_ALL || (r->OperationType & OperationType) == OperationType) + { + ret = r->ActionType; + + + /* remember which rule caused this alert */ + *PolicyLineNumber = r->PolicyLineNumber; + *RuleNumber = r->RuleNumber; + + + LOG(LOG_SS_POLICY, LOG_PRIORITY_VERBOSE, ("%d PolicyCheckPolicy: %s access to %d\n", + CURRENT_PROCESS_PID, ret >= ACTION_DENY ? "deny" : "permit", + (ULONG) PsGetCurrentProcessId())); + + break; + } + } + + r = (PPOLICY_RULE) r->Next; + } + + KeReleaseSpinLock(&pSecPolicy->SpinLock, irql); + + + return ret; +} + + + +/* + * PolicyCheck() + * + * Description: + * Verifies whether a specified action (rule (RuleType) + object (arg) ) are allowed + * by a global and current process security policies. + * + * Parameters: + * RuleType - rule type (file rule, registry, etc). + * Object - Object name. + * OperationType - type of operation carried out (read, write, etc). + * RuleNumber - number of the rule that triggered the alert (if it did) + * PolicyFilename - name of the policy where the rule, that triggered the alert, was specified + * PolicyLineNumber - policy line where the rule, that triggered the alert, was specified + * + * Returns: + * ACCESS_NONE, ACCESS_PERMIT, ACCESS_DENY or ACCESS_QUIETDENY depending on a security policy. + */ + +ACTION_TYPE +PolicyCheck(RULE_TYPE RuleType, PCHAR Object, UCHAR OperationType, UCHAR *RuleNumber, PWSTR *PolicyFilename, USHORT *PolicyLineNumber) +{ + PIMAGE_PID_ENTRY p, prev; + PWSTR GlobalPolicyFilename, ProcessPolicyFilename; + USHORT GlobalPolicyLineNumber, ProcessPolicyLineNumber; + UCHAR GlobalRuleNumber, ProcessRuleNumber; + ACTION_TYPE GlobalAction, ProcessAction = ACTION_PERMIT_DEFAULT, ReturnAction; + ULONG ProcessId = CURRENT_PROCESS_PID; + + + /* don't mess with kernel initiated calls */ + + if (KeGetPreviousMode() != UserMode || KeGetCurrentIrql() != PASSIVE_LEVEL) + + return ACTION_PERMIT; + + + /* + * As most of system calls PolicyCheck() this is a convinient place to verify + * user return address since it needs to be done for all calls + */ + + VerifyUserReturnAddress(); + + + /* + * First verify against the global security policy + */ + + GlobalAction = PolicyCheckPolicy(&gSecPolicy, RuleType, Object, OperationType, gSecPolicy.DefaultPolicyAction, &GlobalRuleNumber, &GlobalPolicyFilename, &GlobalPolicyLineNumber); + + /* + * Then against process specific policy + */ + + /* find the process specific policy */ + + p = FindImagePidEntry(ProcessId, 0); + + if (p) + { + ProcessAction = PolicyCheckPolicy(&p->SecPolicy, RuleType, Object, OperationType, p->SecPolicy.DefaultPolicyAction, &ProcessRuleNumber, &ProcessPolicyFilename, &ProcessPolicyLineNumber); + } + + +// KdPrint(("object %s %d %d action %x %x", Object, RuleType, OperationType, GlobalAction, ProcessAction)); + + /* + * return the most stringent possible action + */ + + /* Exception #1: explicit process permit/log overrides general global deny */ + if ((ProcessAction == ACTION_PERMIT || ProcessAction == ACTION_LOG) && + (GlobalAction & (ACTION_ASK | ACTION_DENY))) + { + *PolicyFilename = ProcessPolicyFilename; + *PolicyLineNumber = ProcessPolicyLineNumber; + *RuleNumber = ProcessRuleNumber; + return ProcessAction; + } + + /* Exception #2: explicit global permit overrides process default deny */ + if (GlobalAction == ACTION_PERMIT && (ProcessAction & ACTION_DEFAULT)) + { + *PolicyFilename = GlobalPolicyFilename; + *PolicyLineNumber = GlobalPolicyLineNumber; + *RuleNumber = GlobalRuleNumber; + return GlobalAction; + } + + if (GlobalAction & ACTION_DENY) + { *PolicyFilename = GlobalPolicyFilename; *PolicyLineNumber = GlobalPolicyLineNumber; *RuleNumber = GlobalRuleNumber; ReturnAction = GlobalAction; goto done; } + + if (ProcessAction & ACTION_DENY) + { *PolicyFilename = ProcessPolicyFilename; *PolicyLineNumber = ProcessPolicyLineNumber; *RuleNumber = ProcessRuleNumber; ReturnAction = ProcessAction; goto done; } + + + if (GlobalAction & ACTION_LOG) + { *PolicyFilename = GlobalPolicyFilename; *PolicyLineNumber = GlobalPolicyLineNumber; *RuleNumber = GlobalRuleNumber; ReturnAction = GlobalAction; goto done; } + + if (ProcessAction & ACTION_LOG) + { *PolicyFilename = ProcessPolicyFilename; *PolicyLineNumber = ProcessPolicyLineNumber; *RuleNumber = ProcessRuleNumber; ReturnAction = ProcessAction; goto done; } + + + if (GlobalAction & ACTION_ASK) + { *PolicyFilename = GlobalPolicyFilename; *PolicyLineNumber = GlobalPolicyLineNumber; *RuleNumber = GlobalRuleNumber; ReturnAction = GlobalAction; goto done; } + + if (ProcessAction & ACTION_ASK) + { *PolicyFilename = ProcessPolicyFilename; *PolicyLineNumber = ProcessPolicyLineNumber; *RuleNumber = ProcessRuleNumber; ReturnAction = ProcessAction; goto done; } + + + if (GlobalAction & ACTION_PERMIT) + { *PolicyFilename = GlobalPolicyFilename; *PolicyLineNumber = GlobalPolicyLineNumber; *RuleNumber = GlobalRuleNumber; ReturnAction = GlobalAction; goto done; } + + if (ProcessAction & ACTION_PERMIT) + { *PolicyFilename = ProcessPolicyFilename; *PolicyLineNumber = ProcessPolicyLineNumber; *RuleNumber = ProcessRuleNumber; ReturnAction = ProcessAction; goto done; } + + + LOG(LOG_SS_POLICY, LOG_PRIORITY_DEBUG, ("object %s %d %d action %x %x\n", Object, RuleType, OperationType, GlobalAction, ProcessAction)); + + *PolicyFilename = NULL; + *PolicyLineNumber = 0; + + + return ProcessAction; + + +done: + + /* + * if we are booting up then don't deny any system calls to prevent a machine + * from not booting up + */ + + if (BootingUp == TRUE && (ReturnAction == ACTION_ASK || (ReturnAction & ACTION_DENY))) + ReturnAction = ACTION_LOG; + + return ReturnAction; +} -- cgit v1.3