Files
2026-04-29 17:10:47 +02:00

910 lines
34 KiB
C++

/*****************************************************************************/
/* AceCondition.cpp Copyright (c) Ladislav Zezula 2023 */
/*---------------------------------------------------------------------------*/
/* Description: */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 26.10.23 1.00 Lad Created */
/*****************************************************************************/
#include "FileTest.h"
LPCTSTR HexaAlphabetLower = _T("0123456789abcdef");
static const CONDITION_OPERATOR Operators[] =
{
{L"Exists", CTOKEN_EXISTS, 0x14, true, true, false, true, true},
{L"Not_Exists", CTOKEN_NOT_EXISTS, 0x14, true, true, false, true, true},
{L"==", CTOKEN_EQUAL, 0x0F, false, false, true, true, false},
{L">=", CTOKEN_GREATER_THAN_OR_EQUAL, 0x0F, false, false, true, true, false},
{L"!=", CTOKEN_NOT_EQUAL, 0x0F, false, false, true, true, false},
{L"<=", CTOKEN_LESS_THAN_OR_EQUAL, 0x0F, false, false, true, true, false},
{L"&&", CTOKEN_AND, 0x0C, false, true, false, false, false},
{L"||", CTOKEN_OR, 0x0B, false, true, false, false, false},
{L"&", CTOKEN_BIT_AND, 0x0A, false, false, true, true, false},
{L"<", CTOKEN_LESS_THAN, 0x0F, false, false, true, true, false},
{L">", CTOKEN_GREATER_THAN, 0x0F, false, false, true, true, false},
{L"Contains", CTOKEN_CONTAINS, 0x0F, false, false, true, true, true},
{L"Not_Contains", CTOKEN_NOT_CONTAINS, 0x0F, false, false, true, true, true},
{L"Any_of", CTOKEN_ANY_OF, 0x0F, false, false, true, true, true},
{L"Not_Any_of", CTOKEN_NOT_ANY_OF, 0x0F, false, false, true, true, true},
{L"!", CTOKEN_NOT, 0x0D, true, true, false, true, false},
{L"Member_of", CTOKEN_MEMBER_OF, 0x0F, true, true, false, true, true},
{L"Not_Member_of", CTOKEN_NOT_MEMBER_OF, 0x0F, true, true, false, true, true},
{L"Device_Member_of", CTOKEN_DEVICE_MEMBER_OF, 0x0F, true, true, false, true, true},
{L"Not_Device_Member_of", CTOKEN_NOT_DEVICE_MEMBER_OF, 0x0F, true, true, false, true, true},
{L"Member_of_any", CTOKEN_MEMBER_OF_ANY, 0x0F, true, true, false, true, true},
{L"Not_Member_of_any", CTOKEN_NOT_MEMBER_OF_ANY, 0x0F, true, true, false, true, true},
{L"Device_Member_of_any", CTOKEN_DEVICE_MEMBER_OF_ANY, 0x0F, true, true, false, true, true},
{L"Not_Device_Member_of_any", CTOKEN_NOT_DEVICE_MEMBER_OF_ANY, 0x0F, true, true, false, true, true},
};
static NTSTATUS UlongAddStringSize(ULONG cbValue, LPCWSTR szString, ULONG * pcbNewValue)
{
ULONG ccString = (ULONG)(wcslen(szString));
ULONG cbString;
// Check overflows
if((cbString = ccString * sizeof(WCHAR)) < ccString)
return STATUS_INTEGER_OVERFLOW;
if((cbValue + cbString) < cbValue)
return STATUS_INTEGER_OVERFLOW;
// Perform the addition
pcbNewValue[0] = cbValue + cbString;
return STATUS_SUCCESS;
}
static bool IsEncodedAttributeChar(UINT ch)
{
LPCWSTR NotEncodedAttributeChar = L"#$\"*+-./:;?@[\\]^_`{}~";
// 0x7F and above are encoded
if(ch < 0x7F)
{
// Alphanumeric chars are NOT encoded
if(isalnum(ch))
return false;
// Exceptions
for(size_t i = 0; NotEncodedAttributeChar[i] != 0; i++)
{
if(ch == NotEncodedAttributeChar[i])
return false;
}
}
return true;
}
static int GetOperatorIndexByToken(ACE_CONDITION_TOKEN TokenCode)
{
for(int i = 0; i < _countof(Operators); i++)
{
if(Operators[i].TokenCode == TokenCode)
{
return i;
}
}
return -1;
}
static DWORD EncodeAttributeName(
LPWSTR Value,
DWORD ValueSize,
LPTSTR * EncodedString)
{
LPTSTR szAttributeName;
size_t nCharsCopied = 0;
// Carefully verify all parameters
if(Value == NULL || ValueSize == 0 || EncodedString == NULL || ValueSize & 0x01)
return ERROR_INVALID_ACL;
// Allocate buffer large enough for encoded attribute name
EncodedString[0] = szAttributeName = (LPTSTR)LocalAlloc(LPTR, (ValueSize * 5) + sizeof(WCHAR));
if(szAttributeName == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Get the number of chars
for(DWORD i = 0; i < ValueSize; i += sizeof(WCHAR))
{
WCHAR chCharacter = Value[i];
// Encoded chars are going to be turned into %ABCD
if(IsEncodedAttributeChar(chCharacter))
{
szAttributeName[nCharsCopied++] = '%';
szAttributeName[nCharsCopied++] = HexaAlphabetLower[(chCharacter >> 0x0C) & 0x0F];
szAttributeName[nCharsCopied++] = HexaAlphabetLower[(chCharacter >> 0x08) & 0x0F];
szAttributeName[nCharsCopied++] = HexaAlphabetLower[(chCharacter >> 0x04) & 0x0F];
szAttributeName[nCharsCopied++] = HexaAlphabetLower[(chCharacter >> 0x00) & 0x0F];
}
// Non-encoded chars are copied as-is
else
{
szAttributeName[nCharsCopied++] = chCharacter;
}
}
// No need to terminate with zero, because it was allocated as LPTR
return ERROR_SUCCESS;
}
static DWORD EncloseSubCondition(LPWSTR * ConditionStr)
{
LPWSTR szSubCondition = ConditionStr[0];
LPWSTR szNewCondition;
size_t ccSubCondition;
size_t ccNewCondition;
size_t cbNewCondition;
// Enclose the subcondition into parentheses, if not done yet
if(szSubCondition[0] != L'(')
{
// Calculate new length
ccSubCondition = wcslen(szSubCondition);
// Verify buffer overflow
if((ccNewCondition = ccSubCondition + 3) < ccSubCondition)
return ERROR_ARITHMETIC_OVERFLOW;
if((cbNewCondition = ccNewCondition * sizeof(WCHAR)) < ccNewCondition)
return ERROR_ARITHMETIC_OVERFLOW;
// Allocate new buffer
if((szNewCondition = (LPWSTR)LocalAlloc(LPTR, cbNewCondition)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Format the new buffer
if(FAILED(StringCbPrintf(szNewCondition, cbNewCondition, L"(%ls)", szSubCondition)))
{
LocalFree(szNewCondition);
return ERROR_NOT_SUPPORTED;
}
// Given the new condition to the caller
ConditionStr[0] = szNewCondition;
LocalFree(szSubCondition);
}
return ERROR_SUCCESS;
}
static DWORD MergeAttributeName(LPCWSTR szPrefix, size_t cbPrefixSize, LPCWSTR szData, size_t cbDataSize, LPWSTR * StrResult)
{
LPWSTR szAttributeName = NULL;
size_t cbFullName = cbPrefixSize + cbDataSize + sizeof(WCHAR);
// Allocate the buffer for the token attribute
StrResult[0] = szAttributeName = (LPWSTR)LocalAlloc(LPTR, cbFullName);
if(szAttributeName != NULL)
{
// Append prefix, if any
if(szPrefix && cbPrefixSize)
memcpy(szAttributeName, szPrefix, cbPrefixSize);
szAttributeName += (cbPrefixSize / sizeof(WCHAR));
// Append data, if any
if(szData && cbDataSize)
memcpy(szAttributeName, szData, cbDataSize);
szAttributeName += (cbDataSize / sizeof(WCHAR));
// Terminate with zero
szAttributeName[0] = 0;
return ERROR_SUCCESS;
}
else
{
return ERROR_NOT_ENOUGH_MEMORY;
}
}
static DWORD GetPrintableAttributeName(
LPBYTE Condition,
ULONG ConditionSize,
ACE_CONDITION_TOKEN Token,
LPTSTR * ValueStr,
DWORD * MoveBy)
{
#define WSTRSZ(wsz)(sizeof(L##wsz) - sizeof(WCHAR))
LPTSTR szAttributeName = NULL;
DWORD cbDataSize;
DWORD dwErrCode;
// Verify parameters
if(Condition == NULL || ValueStr == NULL)
return ERROR_INVALID_PARAMETER;
// Read the data length and get the pointer to data
if(ConditionSize <= 5)
return ERROR_INVALID_ACL;
cbDataSize = *(LPDWORD)(Condition + 1);
Condition += 5;
// Encode any other attribute than CTOKEN_LOCAL_ATTRIBUTE
if(Token != CTOKEN_LOCAL_ATTRIBUTE)
{
LPTSTR szEncodedName = NULL;
size_t cbEncodedName;
dwErrCode = EncodeAttributeName((LPWSTR)(Condition), cbDataSize, &szEncodedName);
if(dwErrCode == ERROR_SUCCESS)
{
// Get the byte length
cbEncodedName = wcslen(szEncodedName) * sizeof(TCHAR) + sizeof(TCHAR);
// Perform merge of the attribute name
switch(Token)
{
case CTOKEN_USER_ATTRIBUTE:
dwErrCode = MergeAttributeName(L"@USER.", WSTRSZ("@USER."), szEncodedName, cbEncodedName, ValueStr);
break;
case CTOKEN_RESOURCE_ATTRIBUTE:
dwErrCode = MergeAttributeName(L"@RESOURCE.", WSTRSZ("@RESOURCE."), szEncodedName, cbEncodedName, ValueStr);
break;
case CTOKEN_DEVICE_ATTRIBUTE:
dwErrCode = MergeAttributeName(L"@DEVICE.", WSTRSZ("@DEVICE."), szEncodedName, cbEncodedName, ValueStr);
break;
case CTOKEN_TOKEN_ATTRIBUTE:
dwErrCode = MergeAttributeName(L"@TOKEN.", WSTRSZ("@TOKEN."), szEncodedName, cbEncodedName, ValueStr);
break;
}
// Free the encoded attribute name
LocalFree(szAttributeName);
}
}
else
{
dwErrCode = MergeAttributeName(NULL, 0, (LPCWSTR)(Condition), cbDataSize, ValueStr);
}
// Write the bytes eaten
if(dwErrCode == ERROR_SUCCESS && MoveBy != NULL)
MoveBy[0] = cbDataSize + 5;
return dwErrCode;
}
static DWORD GetStringForInteger(
LPBYTE Condition,
DWORD ConditionSize,
LPWSTR * ValueStr,
DWORD * MoveBy)
{
ACE_CONDITION_TOKEN TokenCode;
LONG64 IntValue;
LPWSTR szIntegerValue = NULL;
HRESULT hr;
BYTE NumFormat;
BYTE SignByte;
// Read the length of the condition
if((ConditionSize - 1) < 0x0A)
return ERROR_INVALID_ACL;
TokenCode = (ACE_CONDITION_TOKEN)(Condition[0]);
MoveBy[0] = 0x0A;
// Read the integer value, sign byte and format byte
// Note that the integer value is always 64-bit, regardless
// of which CTOKEN_INT## is used.
IntValue = *(ULONG64 *)(Condition + 1);
SignByte = Condition[0x09];
NumFormat = Condition[0x0A];
// Allocate buffer for the largest possible integer
if((ValueStr[0] = szIntegerValue = (LPWSTR)LocalAlloc(LPTR, 0x40)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Check the sign value
switch(SignByte)
{
case 1: *szIntegerValue++ = L'+'; break;
case 2: *szIntegerValue++ = L'-'; IntValue = -IntValue; break;
}
// Perform format-specific printf
switch(NumFormat)
{
case 1:
hr = StringCbPrintf(szIntegerValue, 0x40, _T("%I64o"), IntValue);
break;
case 3:
hr = StringCbPrintf(szIntegerValue, 0x40, _T("0x%I64x"), IntValue);
break;
default:
hr = StringCbPrintf(szIntegerValue, 0x40, _T("%I64u"), IntValue);
break;
}
// Check for failed printf operation
if(FAILED(hr))
{
LocalFree(ValueStr[0]);
return ERROR_NOT_SUPPORTED;
}
return ERROR_SUCCESS;
}
static void GetStringForOctetString(LPWSTR szBuffer, LPBYTE pbOctetString, ULONG cbOctetString)
{
LPBYTE pbEndOfString = pbOctetString + cbOctetString;
// Prefix the converted string with hashtag
*szBuffer++ = '#';
// Convert to hexa string
while(pbOctetString < pbEndOfString)
{
szBuffer[0] = HexaAlphabetLower[(pbOctetString[0] >> 0x04) & 0x0F];
szBuffer[1] = HexaAlphabetLower[(pbOctetString[0] >> 0x00) & 0x0F];
pbOctetString++;
szBuffer += 2;
}
// Terminate buffer with zero
szBuffer[0] = 0;
}
static DWORD LocalGetStringForSid(
PSID pSid,
LPWSTR * String,
void * RootDomainSid,
void * DomainSid,
PSTRSD_SID_LOOKUP tSidLookupDomOrRootDomRelativeTable,
BOOL DefaultToDomain)
{
UNREFERENCED_PARAMETER(tSidLookupDomOrRootDomRelativeTable);
UNREFERENCED_PARAMETER(DefaultToDomain);
UNREFERENCED_PARAMETER(RootDomainSid);
UNREFERENCED_PARAMETER(DomainSid);
// Verify parameters
if(pSid == NULL || String == NULL)
return ERROR_INVALID_PARAMETER;
//
// Here, we skip the call to LookupSidInTable, because we don't have
// a lookup table, so we would only spend lots of time disassembling
//
if(!ConvertSidToStringSidW(pSid, String))
return GetLastError();
return ERROR_SUCCESS;
}
static DWORD GetPrintableOperandValue(
LPBYTE Condition,
DWORD ConditionSize,
LPWSTR * ValueStr,
DWORD * MoveBy,
void * RootDomainSid,
void * DomainSid,
PSTRSD_SID_LOOKUP tSidLookupDomOrRootDomRelativeTable,
BOOL DefaultToDomain)
{
LPWSTR szOperandValue = NULL;
LPWSTR szBuffer = NULL;
size_t cbOperandValue;
DWORD dwErrCode = ERROR_SUCCESS;
DWORD cbDataSize = 0;
BYTE SidBuffer[MAX_SID_LENGTH];
ACE_CONDITION_TOKEN TokenCode;
// Carefully check parameters
if(Condition == NULL || ValueStr == NULL || ConditionSize == 0)
return ERROR_INVALID_PARAMETER;
MoveBy[0] = 1;
// Token-specific
switch(TokenCode = (ACE_CONDITION_TOKEN)(Condition[0]))
{
case CTOKEN_PAD: // 0x00
dwErrCode = ERROR_INVALID_ACL;
break;
case CTOKEN_INT8: // 0x01
case CTOKEN_INT16: // 0x02
case CTOKEN_INT32: // 0x03
case CTOKEN_INT64: // 0x04
if((dwErrCode = GetStringForInteger(Condition, ConditionSize, ValueStr, &cbDataSize)) == ERROR_SUCCESS)
MoveBy[0] = MoveBy[0] + 10;
break;
case CTOKEN_UNICODE_STRING: // 0x10
// Read the length of the unicode string
if((ConditionSize - 1) < 4)
return ERROR_INVALID_ACL;
cbDataSize = *(DWORD *)(Condition + 1);
// Check the length of the SID and data
if((ConditionSize - 5) < cbDataSize)
return ERROR_INVALID_ACL;
MoveBy[0] = 5;
// Check overflow
if((cbOperandValue = cbDataSize + 6) < cbDataSize)
return ERROR_ARITHMETIC_OVERFLOW;
// Allocate buffer
ValueStr[0] = szOperandValue = (LPWSTR)LocalAlloc(LPTR, cbOperandValue);
if(szOperandValue == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Enclose the string into quotation marks
*szOperandValue++ = _T('\"');
memcpy(szOperandValue, Condition + 5, cbDataSize);
szOperandValue[cbDataSize / sizeof(WCHAR)] = _T('\"');
// Increment the number of bytes processed and return
MoveBy[0] = MoveBy[0] + cbDataSize;
break;
case CTOKEN_OCTET_STRING: // 0x18
// Read the length of the octet string
if((ConditionSize - 1) < 4)
return ERROR_INVALID_ACL;
cbDataSize = *(DWORD *)(Condition + 1);
// Check the length of the octet string
if((ConditionSize - 5) < cbDataSize)
return ERROR_INVALID_ACL;
MoveBy[0] = 5;
// Length of the octet string must not be 0
if(cbDataSize == 0)
return ERROR_INVALID_ACL;
// Check overflow
if((cbOperandValue = (cbDataSize + cbDataSize + 2) * sizeof(WCHAR)) < cbDataSize)
return ERROR_ARITHMETIC_OVERFLOW;
// Allocate buffer
ValueStr[0] = szOperandValue = (LPWSTR)LocalAlloc(LPTR, cbOperandValue);
if(szOperandValue == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Encode the string into HEX
GetStringForOctetString(szOperandValue, Condition + 5, cbDataSize);
// Increment the number of bytes processed and return
MoveBy[0] = MoveBy[0] + cbDataSize;
break;
case CTOKEN_COMPOSITE: // 0x50
{
DWORD cbComposite = 0;
DWORD cbOldValue = 6;
DWORD cbNewValue;
DWORD cbMoveBy;
// Read the length of the composite value
if((ConditionSize - 1) < 4)
return ERROR_INVALID_ACL;
cbDataSize = *(DWORD *)(Condition + 1);
// Check the length of the composite value
if((ConditionSize - 5) < cbDataSize)
return ERROR_INVALID_ACL;
cbMoveBy = 5;
// Length of the composite value must not be 0
if(cbDataSize == 0)
return ERROR_INVALID_ACL;
// Allocate buffer for the composite value
if((ValueStr[0] = szOperandValue = (LPWSTR)LocalAlloc(LPTR, cbOldValue)) == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Open the composite string
szOperandValue[0] = '{';
// Keep working
while(cbComposite < cbDataSize)
{
// Can't have composite value inside an existing composite value
if(Condition[cbComposite + cbMoveBy] == CTOKEN_COMPOSITE)
return ERROR_INVALID_ACL;
dwErrCode = GetPrintableOperandValue(Condition + cbComposite + cbMoveBy,
ConditionSize - cbComposite - cbMoveBy,
&szBuffer,
MoveBy,
RootDomainSid,
DomainSid,
tSidLookupDomOrRootDomRelativeTable,
DefaultToDomain);
if(dwErrCode != ERROR_SUCCESS)
break;
cbComposite = cbComposite + MoveBy[0];
// Check for arithmetic overflow
if(!NT_SUCCESS(UlongAddStringSize(cbOldValue, szBuffer, &cbNewValue)))
return ERROR_ARITHMETIC_OVERFLOW;
if((cbNewValue + 4) < cbNewValue)
return ERROR_ARITHMETIC_OVERFLOW;
cbNewValue = cbNewValue + 4;
// Enlarge the buffer
if((ValueStr[0] = szOperandValue = (LPWSTR)LocalReAlloc(ValueStr[0], cbNewValue, LMEM_MOVEABLE)) == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Format the value part
if(FAILED(StringCbPrintf(szOperandValue + (cbOldValue - 4) / sizeof(WCHAR), cbNewValue - cbOldValue + 4, _T("%ls, "), szBuffer)))
{
dwErrCode = ERROR_NOT_SUPPORTED;
break;
}
// Free the buffer
cbOldValue = cbNewValue;
LocalFree(szBuffer);
szBuffer = NULL;
}
// Terminate the composite value
szOperandValue[cbOldValue / sizeof(WCHAR) - 4] = '}';
szOperandValue[cbOldValue / sizeof(WCHAR) - 3] = 0;
MoveBy[0] = cbMoveBy + cbComposite;
break;
}
case CTOKEN_SID: // 0x51
{
DWORD cbSidString = 12; // Length of "SID()\0" in UNICODE
// Read the length of the SID
if((ConditionSize - 5) < cbDataSize)
return ERROR_INVALID_ACL;
MoveBy[0] = 5;
// Check the length of the SID and data
cbDataSize = *(DWORD *)(Condition + 1);
if(cbDataSize > sizeof(SidBuffer))
return ERROR_INVALID_ACL;
// Copy the SID
memcpy(SidBuffer, Condition + 5, cbDataSize);
dwErrCode = LocalGetStringForSid(SidBuffer, &szBuffer, RootDomainSid, DomainSid, tSidLookupDomOrRootDomRelativeTable, DefaultToDomain);
if(dwErrCode != ERROR_SUCCESS)
break;
// Check overflow
if(!NT_SUCCESS(UlongAddStringSize(cbSidString, szBuffer, &cbSidString)))
{
dwErrCode = ERROR_ARITHMETIC_OVERFLOW;
break;
}
// Allocate buffer for the operand value
if((ValueStr[0] = szOperandValue = (LPWSTR)LocalAlloc(LPTR, cbSidString)) == NULL)
{
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Format the SID into the operand value
if(FAILED(StringCbPrintf(szOperandValue, cbSidString, _T("SID(%ls)"), szBuffer)))
{
dwErrCode = ERROR_NOT_SUPPORTED;
break;
}
// Increment the number of bytes processed and return
MoveBy[0] = MoveBy[0] + cbDataSize;
break;
}
default:
dwErrCode = ERROR_INVALID_ACL;
break;
}
// Free the inner buffer and exit
if(szBuffer != NULL)
LocalFree(szBuffer);
return dwErrCode;
}
static DWORD inline LGSFP_Cleanup(
LPWSTR * OperandArray,
ULONG dwOperandsLevel0,
DWORD dwErrCode = ERROR_INVALID_ACL,
DWORD dwDefErrCode = ERROR_INVALID_ACL)
{
if(dwOperandsLevel0 != 0)
{
if(dwErrCode == ERROR_SUCCESS)
dwErrCode = dwDefErrCode;
while(dwOperandsLevel0 > 0)
LocalFree(OperandArray[dwOperandsLevel0--]);
}
return dwErrCode;
}
static DWORD inline LGSFP_CleanupWithFree(
LPVOID pvPointerToFree,
LPWSTR * OperandArray,
ULONG OperandCount,
DWORD dwErrCode)
{
if(pvPointerToFree != NULL)
LocalFree(pvPointerToFree);
return LGSFP_Cleanup(OperandArray, OperandCount, dwErrCode, ERROR_INVALID_ACL);
}
// advapi32.dll!_LocalpGetStringForCondition@32
// (also aclui.dll!_LocalpGetStringForCondition@32)
DWORD LocalGetStringForCondition(
LPBYTE Condition,
DWORD ConditionSize,
LPWSTR * ConditionStr,
DWORD * pdwReferencedTokenTypes,
void * RootDomainSid,
void * DomainSid,
PSTRSD_SID_LOOKUP tSidLookupDomOrRootDomRelativeTable,
bool DefaultToDomain)
{
LPWSTR OperandArray[MAX_OPERAND_STACK + 2];
DWORD dwOperandsLevel0 = 0; // r13d
DWORD dwOperandsLevel1 = 0; // r14
DWORD cbTotalSize;
DWORD cbTokenSize = 0;
DWORD dwErrCode = ERROR_SUCCESS;
ACE_CONDITION_TOKEN TokenCode;
UNREFERENCED_PARAMETER(pdwReferencedTokenTypes);
if(Condition == NULL || ConditionStr == NULL || ConditionSize == 0)
return ERROR_INVALID_PARAMETER;
if(ConditionSize < 6 || *(LPDWORD)(Condition) != 0x78747261)
return ERROR_INVALID_ACE_CONDITION;
cbTotalSize = 4;
// Keep going as long as we have some data to load
while((cbTotalSize = cbTotalSize + cbTokenSize) < ConditionSize)
{
LPTSTR szTokenName = NULL;
// Verify the current nest level
if(dwOperandsLevel1 == MAX_OPERAND_STACK)
return ERROR_STACK_OVERFLOW;
cbTokenSize = 0;
// Take the next byte and examine its meaning
switch(TokenCode = (ACE_CONDITION_TOKEN)(Condition[cbTotalSize]))
{
case CTOKEN_PAD: // 0x00
// Skip all padding bytes
while(cbTotalSize < ConditionSize && Condition[cbTotalSize] == 0)
cbTotalSize++;
// Are we at the end of the condition?
if(cbTotalSize != ConditionSize)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
break;
case CTOKEN_INT8: // 0x01
case CTOKEN_INT16: // 0x02
case CTOKEN_INT32: // 0x03
case CTOKEN_INT64: // 0x04
case CTOKEN_UNICODE_STRING: // 0x10
case CTOKEN_OCTET_STRING: // 0x18
case CTOKEN_COMPOSITE: // 0x50
case CTOKEN_SID: // 0x51
dwErrCode = GetPrintableOperandValue(Condition + cbTotalSize,
ConditionSize - cbTotalSize,
&szTokenName,
&cbTokenSize,
RootDomainSid,
DomainSid,
tSidLookupDomOrRootDomRelativeTable,
DefaultToDomain);
if(dwErrCode != ERROR_SUCCESS)
return LGSFP_CleanupWithFree(szTokenName, OperandArray, dwOperandsLevel0, dwErrCode);
// Insert the operand to the operand array
OperandArray[++dwOperandsLevel1] = szTokenName;
dwOperandsLevel0++;
szTokenName = NULL;
break;
case CTOKEN_EQUAL: // 0x80
case CTOKEN_NOT_EQUAL: // 0x81
case CTOKEN_LESS_THAN: // 0x82
case CTOKEN_LESS_THAN_OR_EQUAL: // 0x83
case CTOKEN_GREATER_THAN: // 0x84
case CTOKEN_GREATER_THAN_OR_EQUAL: // 0x85
case CTOKEN_CONTAINS: // 0x86
case CTOKEN_EXISTS: // 0x87
case CTOKEN_ANY_OF: // 0x88
case CTOKEN_MEMBER_OF: // 0x89
case CTOKEN_DEVICE_MEMBER_OF: // 0x8a
case CTOKEN_MEMBER_OF_ANY: // 0x8b
case CTOKEN_DEVICE_MEMBER_OF_ANY: // 0x8c
case CTOKEN_NOT_EXISTS: // 0x8d
case CTOKEN_NOT_CONTAINS: // 0x8e
case CTOKEN_NOT_ANY_OF: // 0x8f
case CTOKEN_NOT_MEMBER_OF: // 0x90
case CTOKEN_NOT_DEVICE_MEMBER_OF: // 0x91
case CTOKEN_NOT_MEMBER_OF_ANY: // 0x92
case CTOKEN_NOT_DEVICE_MEMBER_OF_ANY: // 0x93
case CTOKEN_AND: // 0xa0
case CTOKEN_OR: // 0xa1
case CTOKEN_NOT: // 0xa2
{
LPCWSTR szOperator;
LPWSTR szExpression = NULL; // rsi
DWORD cbExpression;
DWORD cbOperator;
int nOperatorIndex;
// For binary operators, there must be at least two operands
if((TokenCode == CTOKEN_AND || TokenCode == CTOKEN_OR) && (dwOperandsLevel1 < 2))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
// Special for NOT: need to enclose the operand in parentheses
if(TokenCode == CTOKEN_NOT)
{
if(dwOperandsLevel1 < 1)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
if((dwErrCode = EncloseSubCondition(&OperandArray[dwOperandsLevel0])) != ERROR_SUCCESS)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, dwErrCode);
}
// Find the operator in the array
if((nOperatorIndex = GetOperatorIndexByToken(TokenCode)) < 0)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
szOperator = Operators[nOperatorIndex].Op;
cbOperator = (DWORD)(wcslen(szOperator) * sizeof(WCHAR));
// Is it an unary operator?
if(Operators[nOperatorIndex].Unary)
{
LPCWSTR szFormatString;
LPWSTR szOperand; // rdi
// There must be at least 1 operand
if(dwOperandsLevel1 < 1)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
szOperand = OperandArray[dwOperandsLevel1];
if(!NT_SUCCESS(UlongAddStringSize(cbOperator, szOperand, &cbOperator)))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_ARITHMETIC_OVERFLOW);
// Allocate expression string
if((szExpression = (LPWSTR)LocalAlloc(LPTR, (cbExpression = cbOperator + 8))) == NULL)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_NOT_ENOUGH_MEMORY);
// If not CTOKEN_NOT, we need to check operand
if(TokenCode != CTOKEN_NOT)
{
// The operand must NOT contain user, device or token
if(TokenCode == CTOKEN_EXISTS || TokenCode == CTOKEN_MEMBER_OF_ANY)
{
if(!_wcsnicmp(szOperand, L"@USER.", 6) ||
!_wcsnicmp(szOperand, L"@TOKEN.", 7) ||
!_wcsnicmp(szOperand, L"@DEVICE.", 8))
{
return LGSFP_CleanupWithFree(szExpression, OperandArray, dwOperandsLevel0, ERROR_INVALID_ACL);
}
}
szFormatString = L"(%ls %ls)";
}
else
{
szFormatString = L"(%ls%ls)";
}
// Format the binary operation
if(FAILED(StringCbPrintf(szExpression, cbExpression, szFormatString, szOperator, szOperand)))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_NOT_SUPPORTED);
// Replace the current operand with the new one
OperandArray[dwOperandsLevel1] = szExpression;
LocalFree(szOperand);
// Prepare next token
cbTokenSize = 1;
}
else
{
LPWSTR szOperand1; // rdi
LPWSTR szOperand2; // r12
// There must be at least 2 operands
if(dwOperandsLevel1 < 2)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
// Add size of operator and size of operand2
szOperand2 = OperandArray[dwOperandsLevel1];
if(!NT_SUCCESS(UlongAddStringSize(cbOperator, szOperand2, &cbOperator)))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_ARITHMETIC_OVERFLOW);
// Add size of operator and size of operand1
szOperand1 = OperandArray[dwOperandsLevel1 - 1];
if(!NT_SUCCESS(UlongAddStringSize(cbOperator, szOperand1, &cbOperator)))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_ARITHMETIC_OVERFLOW);
// Allocate expression string
if((szExpression = (LPWSTR)LocalAlloc(LPTR, (cbExpression = cbOperator + 10))) == NULL)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_NOT_ENOUGH_MEMORY);
// Format the binary operation
if(FAILED(StringCbPrintf(szExpression, cbExpression, L"(%ls %ls %ls)", szOperand1, szOperator, szOperand2)))
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_NOT_SUPPORTED);
// Free both operands
LocalFree(szOperand2);
LocalFree(szOperand1);
// Remove operands from the operand array
OperandArray[dwOperandsLevel1--] = NULL;
OperandArray[dwOperandsLevel1] = szExpression;
dwOperandsLevel0--;
// Prepare next token
cbTokenSize = 1;
}
break;
}
case CTOKEN_LOCAL_ATTRIBUTE: // 0xf8
case CTOKEN_USER_ATTRIBUTE: // 0xf9
case CTOKEN_RESOURCE_ATTRIBUTE: // 0xfa
case CTOKEN_DEVICE_ATTRIBUTE: // 0xfb
dwErrCode = GetPrintableAttributeName(Condition + cbTotalSize,
ConditionSize - cbTotalSize,
TokenCode,
&szTokenName,
&cbTokenSize);
if(dwErrCode != ERROR_SUCCESS)
return LGSFP_CleanupWithFree(szTokenName, OperandArray, dwOperandsLevel0, dwErrCode);
// Insert the attribute name to the operand array
OperandArray[++dwOperandsLevel1] = szTokenName;
dwOperandsLevel0++;
szTokenName = NULL;
break;
default:
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0);
}
}
// There must be exactly one final operand at this point
if(dwOperandsLevel0 != 1)
return LGSFP_Cleanup(OperandArray, dwOperandsLevel0, ERROR_SUCCESS);
ConditionStr[0] = OperandArray[1];
// Make sure that we enclosed the condition into parentheses
if((dwErrCode = EncloseSubCondition(ConditionStr)) != ERROR_SUCCESS)
LGSFP_Cleanup(OperandArray, dwOperandsLevel0, dwErrCode);
return ERROR_SUCCESS;
}