Sandboxie/Sandboxie/core/drv/conf_user.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;
}