600 lines
16 KiB
C
600 lines
16 KiB
C
/*
|
|
* Copyright 2004-2020 Sandboxie Holdings, LLC
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Configuration (User Management)
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
#include "conf.h"
|
|
#include "api.h"
|
|
#include "process.h"
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Structures
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
typedef struct _CONF_USER {
|
|
|
|
LIST_ELEM list_elem;
|
|
ULONG len;
|
|
ULONG sid_len;
|
|
ULONG name_len;
|
|
WCHAR *sid;
|
|
WCHAR *name;
|
|
WCHAR space[1];
|
|
|
|
} CONF_USER;
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Variables
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
static LIST Conf_Users;
|
|
static PERESOURCE Conf_Users_Lock = NULL;
|
|
static KEVENT *Conf_Users_Event = NULL;
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Functions
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
static BOOLEAN Conf_GetUserNameForSid(
|
|
const WCHAR *SidString, ULONG SessionId, WCHAR *varvalue);
|
|
|
|
static BOOLEAN Conf_GetGroupsForSid(WCHAR *PageSizeBuffer, ULONG SessionId);
|
|
|
|
static BOOLEAN Conf_FindUserName(const WCHAR *needle, const WCHAR *haystack);
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
static NTSTATUS Conf_Api_SetUserName(PROCESS *proc, ULONG64 *parms);
|
|
|
|
static NTSTATUS Conf_Api_IsBoxEnabled(PROCESS *proc, ULONG64 *parms);
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text (INIT, Conf_Init_User)
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_Init_User
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_Init_User(void)
|
|
{
|
|
List_Init(&Conf_Users);
|
|
|
|
Conf_Users_Event = ExAllocatePoolWithTag(NonPagedPool, sizeof(KEVENT), tzuk);
|
|
if (! Conf_Users_Event) {
|
|
Log_Msg0(MSG_1104);
|
|
return FALSE;
|
|
}
|
|
KeInitializeEvent(Conf_Users_Event, SynchronizationEvent, FALSE);
|
|
|
|
if (! Mem_GetLockResource(&Conf_Users_Lock, TRUE))
|
|
return FALSE;
|
|
|
|
Api_SetFunction(API_SET_USER_NAME, Conf_Api_SetUserName);
|
|
Api_SetFunction(API_IS_BOX_ENABLED, Conf_Api_IsBoxEnabled);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_Unload_User
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX void Conf_Unload_User(void)
|
|
{
|
|
if (Conf_Users_Event) {
|
|
ExFreePoolWithTag(Conf_Users_Event, tzuk);
|
|
Conf_Users_Event = NULL;
|
|
}
|
|
|
|
Mem_FreeLockResource(&Conf_Users_Lock);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_GetUserNameForSid
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_GetUserNameForSid(
|
|
const WCHAR *SidString, ULONG SessionId, WCHAR *varvalue)
|
|
{
|
|
static const WCHAR *_unknown = L"unknown";
|
|
ULONG sid_len;
|
|
ULONG retries;
|
|
BOOLEAN message_sent;
|
|
|
|
sid_len = wcslen(SidString);
|
|
message_sent = FALSE;
|
|
|
|
for (retries = 0; (retries < 40) && (! Driver_Unloading); ++retries) {
|
|
|
|
//
|
|
// look for our sid in the list of known Conf_Users
|
|
//
|
|
|
|
CONF_USER *user1;
|
|
LARGE_INTEGER time;
|
|
KIRQL irql;
|
|
|
|
KeRaiseIrql(APC_LEVEL, &irql);
|
|
ExAcquireResourceExclusiveLite(Conf_Users_Lock, TRUE);
|
|
|
|
user1 = List_Head(&Conf_Users);
|
|
while (user1) {
|
|
if (user1->sid_len == sid_len &&
|
|
_wcsicmp(user1->sid, SidString) == 0)
|
|
break;
|
|
user1 = List_Next(user1);
|
|
}
|
|
|
|
//
|
|
// if we found our SID, translate the user name
|
|
//
|
|
|
|
if (user1 && varvalue)
|
|
wcscpy(varvalue, user1->name);
|
|
|
|
ExReleaseResourceLite(Conf_Users_Lock);
|
|
KeLowerIrql(irql);
|
|
|
|
if (user1)
|
|
return TRUE;
|
|
|
|
//
|
|
// we couldn't find the SID, so ask SbieSvc to translate
|
|
//
|
|
|
|
if (! message_sent) {
|
|
|
|
ULONG msg_len = sizeof(SVC_LOOKUP_SID_MSG)
|
|
+ (sid_len + 4) * sizeof(WCHAR);
|
|
|
|
SVC_LOOKUP_SID_MSG *msg = Mem_Alloc(Driver_Pool, msg_len);
|
|
if (! msg)
|
|
break;
|
|
|
|
msg->session_id = SessionId;
|
|
wcscpy(msg->sid_string, SidString);
|
|
|
|
message_sent =
|
|
Api_SendServiceMessage(SVC_LOOKUP_SID, msg_len, msg);
|
|
|
|
Mem_Free(msg, msg_len);
|
|
|
|
if (! message_sent)
|
|
break;
|
|
}
|
|
|
|
//
|
|
// now wait for SbieSvc to report back
|
|
//
|
|
|
|
/*Conf_AdjustUseCount(TRUE);
|
|
time.QuadPart = -(SECONDS(1) * 7);
|
|
KeDelayExecutionThread(KernelMode, FALSE, &time);
|
|
Conf_AdjustUseCount(FALSE);*/
|
|
|
|
time.QuadPart = -(SECONDS(1) / 4);
|
|
KeWaitForSingleObject(
|
|
Conf_Users_Event, Executive, KernelMode, FALSE, &time);
|
|
}
|
|
|
|
Log_Msg1(MSG_CONF_USER_NAME, SidString);
|
|
if (varvalue)
|
|
wcscpy(varvalue, _unknown);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_Expand_UserName
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_Expand_UserName(CONF_EXPAND_ARGS *args, WCHAR *varvalue)
|
|
{
|
|
return Conf_GetUserNameForSid(args->sid, *args->session, varvalue);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_GetGroupsForSid
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_GetGroupsForSid(WCHAR *PageSizeBuffer, ULONG SessionId)
|
|
{
|
|
UNICODE_STRING uni;
|
|
PACCESS_TOKEN pAccessToken;
|
|
TOKEN_GROUPS *groups;
|
|
WCHAR *bufptr;
|
|
NTSTATUS status;
|
|
ULONG i;
|
|
|
|
bufptr = PageSizeBuffer;
|
|
|
|
pAccessToken = PsReferencePrimaryToken(PsGetCurrentProcess());
|
|
status = SeQueryInformationToken(pAccessToken, TokenGroups, &groups);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
for (i = 0; i < groups->GroupCount; ++i) {
|
|
|
|
ULONG attrs = groups->Groups[i].Attributes;
|
|
if (! (attrs & SE_GROUP_ENABLED))
|
|
continue;
|
|
if (attrs & (SE_GROUP_USE_FOR_DENY_ONLY | SE_GROUP_LOGON_ID |
|
|
SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED))
|
|
continue;
|
|
|
|
uni.Length = 0;
|
|
uni.MaximumLength = 512;
|
|
uni.Buffer = bufptr;
|
|
|
|
status = RtlConvertSidToUnicodeString(
|
|
&uni, groups->Groups[i].Sid, FALSE);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
if (! Conf_GetUserNameForSid(bufptr, SessionId, bufptr))
|
|
status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
bufptr[0] = L'?';
|
|
bufptr[1] = L'\0';
|
|
}
|
|
|
|
bufptr += wcslen(bufptr) + 1;
|
|
if (bufptr - PageSizeBuffer > 1024)
|
|
break;
|
|
}
|
|
|
|
ExFreePool(groups);
|
|
|
|
//
|
|
// on Windows Vista, check for UAC split token
|
|
//
|
|
|
|
if (Driver_OsVersion >= DRIVER_WINDOWS_VISTA &&
|
|
(bufptr - PageSizeBuffer < 1024)) {
|
|
|
|
ULONG *pElevationType;
|
|
NTSTATUS status = SeQueryInformationToken(
|
|
pAccessToken, TokenElevationType, &pElevationType);
|
|
if (NT_SUCCESS(status)) {
|
|
if (*pElevationType == TokenElevationTypeFull ||
|
|
*pElevationType == TokenElevationTypeLimited) {
|
|
|
|
wcscpy(bufptr, L"Administrators");
|
|
bufptr += wcslen(bufptr) + 1;
|
|
}
|
|
ExFreePool(pElevationType);
|
|
}
|
|
}
|
|
}
|
|
|
|
*bufptr = L'\0';
|
|
|
|
PsDereferencePrimaryToken(pAccessToken);
|
|
|
|
return (NT_SUCCESS(status) ? TRUE : FALSE);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_FindUserName
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_FindUserName(const WCHAR *needle, const WCHAR *haystack)
|
|
{
|
|
const WCHAR *needle_ptr;
|
|
|
|
if (! (*needle))
|
|
return FALSE;
|
|
|
|
while (1) {
|
|
|
|
while (*haystack == L' ' || *haystack == L'\t' || *haystack == L',')
|
|
++haystack;
|
|
if (! (*haystack))
|
|
break;
|
|
|
|
for (needle_ptr = needle; *needle_ptr; ++needle_ptr) {
|
|
WCHAR x = *haystack;
|
|
++haystack;
|
|
if (x == L',')
|
|
break;
|
|
x = (x == L' ') ? L'_' : towlower(x);
|
|
if (x != towlower(*needle_ptr))
|
|
break;
|
|
}
|
|
|
|
if (! (*needle_ptr)) {
|
|
while (*haystack == L' ' || *haystack == L'\t')
|
|
++haystack;
|
|
if (*haystack == L',' || (! (*haystack)))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_IsBoxEnabled
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX BOOLEAN Conf_IsBoxEnabled(
|
|
const WCHAR *BoxName, const WCHAR *SidString, ULONG SessionId)
|
|
{
|
|
const WCHAR *value;
|
|
WCHAR *buffer;
|
|
BOOLEAN enabled;
|
|
|
|
//
|
|
// expect setting Enabled=y,
|
|
// and potentially Enabled=y,user1,user2,...
|
|
//
|
|
|
|
enabled = FALSE;
|
|
|
|
Conf_AdjustUseCount(TRUE);
|
|
|
|
value = Conf_Get(BoxName, L"Enabled", CONF_GET_NO_GLOBAL);
|
|
if ((! value) || (*value != L'y' && *value != L'Y'))
|
|
goto release_and_return;
|
|
|
|
value = wcschr(value, L',');
|
|
if (! value) {
|
|
enabled = TRUE;
|
|
goto release_and_return;
|
|
}
|
|
|
|
//
|
|
// check if the username or any of the groupnames
|
|
// appears in the Enabled setting
|
|
//
|
|
|
|
buffer = ExAllocatePoolWithTag(PagedPool, PAGE_SIZE, tzuk);
|
|
if (buffer) {
|
|
|
|
if (Conf_GetUserNameForSid(SidString, SessionId, buffer)) {
|
|
|
|
if (Conf_FindUserName(buffer, value))
|
|
enabled = TRUE;
|
|
|
|
else if (Conf_GetGroupsForSid(buffer, SessionId)) {
|
|
|
|
WCHAR *group = buffer;
|
|
while (*group) {
|
|
|
|
if (Conf_FindUserName(group, value)) {
|
|
enabled = TRUE;
|
|
break;
|
|
}
|
|
|
|
group += wcslen(group) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExFreePoolWithTag(buffer, tzuk);
|
|
}
|
|
|
|
release_and_return:
|
|
|
|
Conf_AdjustUseCount(FALSE);
|
|
|
|
return enabled;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_Api_SetUserName
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
NTSTATUS Conf_Api_SetUserName(PROCESS *proc, ULONG64 *parms)
|
|
{
|
|
API_SET_USER_NAME_ARGS *args = (API_SET_USER_NAME_ARGS *)parms;
|
|
NTSTATUS status;
|
|
UNICODE_STRING64 *user_uni;
|
|
WCHAR *user_sid, *user_name;
|
|
ULONG user_sid_len, user_name_len;
|
|
CONF_USER *user, *user1;
|
|
ULONG user_len;
|
|
KIRQL irql;
|
|
|
|
//
|
|
// this API must be invoked by the Sandboxie service
|
|
//
|
|
|
|
if (proc || (PsGetCurrentProcessId() != Api_ServiceProcessId)) {
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// probe user target path parameter (sidstring and username)
|
|
//
|
|
|
|
user_uni = args->sidstring.val;
|
|
if (! user_uni)
|
|
return STATUS_INVALID_PARAMETER;
|
|
ProbeForRead(user_uni, sizeof(UNICODE_STRING64), sizeof(ULONG64));
|
|
|
|
user_sid = (WCHAR *)user_uni->Buffer;
|
|
user_sid_len = user_uni->Length & ~1;
|
|
if ((! user_sid) || (! user_sid_len) || (user_sid_len > 1024))
|
|
return STATUS_INVALID_PARAMETER;
|
|
ProbeForRead(user_sid, user_sid_len, sizeof(WCHAR));
|
|
|
|
user_uni = args->username.val;
|
|
if (! user_uni)
|
|
return STATUS_INVALID_PARAMETER;
|
|
ProbeForRead(user_uni, sizeof(UNICODE_STRING64), sizeof(ULONG64));
|
|
|
|
user_name = (WCHAR *)user_uni->Buffer;
|
|
user_name_len = user_uni->Length & ~1;
|
|
if ((! user_name) || (! user_name_len) || (user_name_len > 1024))
|
|
return STATUS_INVALID_PARAMETER;
|
|
ProbeForRead(user_name, user_name_len, sizeof(WCHAR));
|
|
|
|
//
|
|
// create a CONF_USER element
|
|
//
|
|
|
|
user_len = sizeof(CONF_USER) + user_sid_len + user_name_len + 8;
|
|
user = Mem_Alloc(Driver_Pool, user_len);
|
|
if (! user)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
__try {
|
|
|
|
user->len = user_len;
|
|
|
|
user->sid = &user->space[0];
|
|
memcpy(user->sid, user_sid, user_sid_len);
|
|
user->sid[user_sid_len / sizeof(WCHAR)] = L'\0';
|
|
user->sid_len = wcslen(user->sid);
|
|
|
|
user->name = user->sid + user->sid_len + 1;
|
|
memcpy(user->name, user_name, user_name_len);
|
|
user->name[user_name_len / sizeof(WCHAR)] = L'\0';
|
|
user->name_len = wcslen(user->name);
|
|
|
|
while (1) {
|
|
WCHAR *ptr = wcschr(user->name, L'\\');
|
|
if (! ptr)
|
|
ptr = wcschr(user->name, L' ');
|
|
if (! ptr)
|
|
break;
|
|
*ptr = L'_';
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
status = GetExceptionCode();
|
|
}
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
Mem_Free(user, user_len);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// remove an existing CONF_USER element if we find one matching
|
|
// the new CONF_USER element. then add the new entry
|
|
//
|
|
|
|
KeRaiseIrql(APC_LEVEL, &irql);
|
|
ExAcquireResourceExclusiveLite(Conf_Users_Lock, TRUE);
|
|
|
|
user1 = List_Head(&Conf_Users);
|
|
while (user1) {
|
|
|
|
if (user1->sid_len == user->sid_len &&
|
|
_wcsicmp(user1->sid, user->sid) == 0) {
|
|
|
|
List_Remove(&Conf_Users, user1);
|
|
Mem_Free(user1, user1->len);
|
|
break;
|
|
}
|
|
|
|
user1 = List_Next(user1);
|
|
}
|
|
|
|
List_Insert_After(&Conf_Users, NULL, user);
|
|
|
|
ExReleaseResourceLite(Conf_Users_Lock);
|
|
KeLowerIrql(irql);
|
|
|
|
KeSetEvent(Conf_Users_Event, 0, FALSE);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Conf_Api_IsBoxEnabled
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS Conf_Api_IsBoxEnabled(PROCESS *proc, ULONG64 *parms)
|
|
{
|
|
API_IS_BOX_ENABLED_ARGS *args = (API_IS_BOX_ENABLED_ARGS *)parms;
|
|
NTSTATUS status;
|
|
ULONG SessionId;
|
|
UNICODE_STRING SidString;
|
|
const WCHAR* sid;
|
|
WCHAR boxname[BOXNAME_COUNT];
|
|
|
|
if (! Api_CopyBoxNameFromUser(boxname, (WCHAR *)args->box_name.val))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (args->sid_string.val != NULL) {
|
|
sid = args->sid_string.val;
|
|
SessionId = args->session_id.val;
|
|
SidString.Buffer = NULL;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
status = Process_GetSidStringAndSessionId(
|
|
NtCurrentProcess(), NULL, &SidString, &SessionId);
|
|
sid = SidString.Buffer;
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
status = Conf_IsValidBox(boxname);
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
if (! Conf_IsBoxEnabled(boxname, sid, SessionId))
|
|
status = STATUS_ACCOUNT_RESTRICTION;
|
|
}
|
|
|
|
RtlFreeUnicodeString(&SidString);
|
|
}
|
|
|
|
return status;
|
|
}
|