Sandboxie/Sandboxie/core/drv/conf.c

1735 lines
45 KiB
C

/*
* Copyright 2004-2020 Sandboxie Holdings, LLC
* Copyright 2020-2023 David Xanatos, xanasoft.com
*
* 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
//---------------------------------------------------------------------------
#include "conf.h"
#include "process.h"
#include "api.h"
#include "api_flags.h"
#include "obj.h"
#include "util.h"
#define KERNEL_MODE
#include "common/stream.h"
#include "common/my_version.h"
//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------
#define USE_CONF_MAP
#define CONF_LINE_LEN 2000 // keep in sync with sbieiniwire.h
#define CONF_MAX_LINES 100000 // keep in sync with sbieiniwire.h
#define CONF_TMPL_LINE_BASE 0x01000000
//---------------------------------------------------------------------------
// Structures
//---------------------------------------------------------------------------
//
// Note: we want to preserve the order of the settings when enumerating
// hence we can not replace the list with a hash map entirely
// instead we use both, here the hash map is used only for lookups
// the keys in the map are only pointers to the name fields in the list entries
//
typedef struct _CONF_DATA {
POOL *pool;
LIST sections; // CONF_SECTION
#ifdef USE_CONF_MAP
HASH_MAP sections_map;
#endif
ULONG home; // 1 if configuration read from Driver_Home_Path
WCHAR* path;
ULONG encoding; // 0 - unicode, 1 - utf8, 2 - unicode (byte swapped)
volatile ULONG use_count;
} CONF_DATA;
typedef struct _CONF_SECTION {
LIST_ELEM list_elem;
WCHAR *name;
LIST settings; // CONF_SETTING
#ifdef USE_CONF_MAP
HASH_MAP settings_map;
#endif
BOOLEAN from_template;
} CONF_SECTION;
typedef struct _CONF_SETTING {
LIST_ELEM list_elem;
WCHAR *name;
WCHAR *value;
BOOLEAN from_template;
BOOLEAN template_handled;
} CONF_SETTING;
//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------
static NTSTATUS Conf_Read(ULONG session_id);
static NTSTATUS Conf_Read_Sections(
STREAM *stream, CONF_DATA *data, int *linenum);
static NTSTATUS Conf_Read_Settings(
STREAM *stream, CONF_DATA *data, CONF_SECTION *section,
WCHAR *line, int *linenum);
NTSTATUS Conf_Read_Line(STREAM *stream, WCHAR *line, int *linenum);
static NTSTATUS Conf_Merge_Templates(CONF_DATA *data, ULONG session_id);
static NTSTATUS Conf_Merge_Global(
CONF_DATA *data, ULONG session_id,
CONF_SECTION *global);
static NTSTATUS Conf_Merge_Template(
CONF_DATA *data, ULONG session_id,
const WCHAR *tmpl_name, CONF_SECTION *section, const WCHAR* name);
static const WCHAR *Conf_Get_Helper(
const WCHAR *section_name, const WCHAR *setting_name,
ULONG *index, BOOLEAN skip_tmpl);
static const WCHAR *Conf_Get_Section_Name(ULONG index, BOOLEAN skip_tmpl);
static const WCHAR *Conf_Get_Setting_Name(
const WCHAR *section_name, ULONG index, BOOLEAN skip_tmpl);
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, Conf_Init)
#endif // ALLOC_PRAGMA
//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------
static CONF_DATA Conf_Data;
static PERESOURCE Conf_Lock = NULL;
static const WCHAR *Conf_GlobalSettings = L"GlobalSettings";
static const WCHAR *Conf_UserSettings_ = L"UserSettings_";
static const WCHAR *Conf_Template_ = L"Template_";
static const WCHAR *Conf_DefaultTemplates = L"DefaultTemplates";
const WCHAR *Conf_TemplateSettings = L"TemplateSettings";
static const WCHAR *Conf_Template = L"Template";
const WCHAR *Conf_Tmpl = L"Tmpl.";
static const WCHAR *Conf_H = L"H";
static const WCHAR *Conf_W = L"W";
static const WCHAR* Conf_Unicode = L"U";
static const WCHAR* Conf_UTF8 = L"8";
//---------------------------------------------------------------------------
// Conf_AdjustUseCount
//---------------------------------------------------------------------------
_FX void Conf_AdjustUseCount(BOOLEAN increase)
{
KIRQL irql;
KeRaiseIrql(APC_LEVEL, &irql);
ExAcquireResourceExclusiveLite(Conf_Lock, TRUE);
if (increase)
InterlockedIncrement(&Conf_Data.use_count);
else
InterlockedDecrement(&Conf_Data.use_count);
ExReleaseResourceLite(Conf_Lock);
KeLowerIrql(irql);
}
//---------------------------------------------------------------------------
// Conf_Read
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Read(ULONG session_id)
{
static const WCHAR *path_sandboxie = L"%s\\" SANDBOXIE_INI;
static const WCHAR *path_templates = L"%s\\Templates.ini";
static const WCHAR *SystemRoot = L"\\SystemRoot";
NTSTATUS status;
CONF_DATA data;
int linenum;
WCHAR linenum_str[32];
ULONG path_len;
WCHAR *path = NULL;
ULONG path_home;
STREAM *stream = NULL;
POOL *pool;
//
// allocate a buffer large enough for \SystemRoot\Sandboxie.ini
// or (Home Path)\Sandboxie.ini
//
path_len = 260 * sizeof(WCHAR);
//path_len = 32; // room for \SystemRoot
if (path_len < wcslen(Driver_HomePathDos) * sizeof(WCHAR) + 64)
path_len = wcslen(Driver_HomePathDos) * sizeof(WCHAR) + 64;
//path_len += 64; // room for \Sandboxie.ini
pool = Pool_Create();
if (! pool)
return STATUS_INSUFFICIENT_RESOURCES;
path = Mem_Alloc(pool, path_len);
if (! path) {
Pool_Delete(pool);
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// try open a custom configuration file, if set
//
UNICODE_STRING IniPath = { 0, (USHORT)path_len - (4 * sizeof(WCHAR)), path };
status = GetRegString(RTL_REGISTRY_ABSOLUTE, Driver_RegistryPath, L"IniPath", &IniPath);
if (NT_SUCCESS(status)) {
if (path[0] != L'\\') {
wmemmove(path + 4, path, (IniPath.Length / sizeof(WCHAR)) + 1);
wmemcpy(path, L"\\??\\", 4);
}
path_home = 2;
status = Stream_Open(
&stream, path,
FILE_GENERIC_READ, 0, FILE_SHARE_READ, FILE_OPEN, 0);
}
//
// open the configuration file, try both places, home first
//
if (!NT_SUCCESS(status)) {
path_home = 1;
RtlStringCbPrintfW(path, path_len, path_sandboxie, Driver_HomePathDos);
status = Stream_Open(
&stream, path, FILE_GENERIC_READ, 0, FILE_SHARE_READ, FILE_OPEN, 0);
}
if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
path_home = 0;
RtlStringCbPrintfW(path, path_len, path_sandboxie, SystemRoot);
status = Stream_Open(
&stream, path,
FILE_GENERIC_READ, 0, FILE_SHARE_READ, FILE_OPEN, 0);
}
if (! NT_SUCCESS(status)) {
if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
status == STATUS_OBJECT_PATH_NOT_FOUND)
{
Log_Msg_Session(MSG_CONF_NO_FILE, NULL, NULL, session_id);
status = STATUS_SUCCESS; // we need to continue and load the Templates.ini with the defaults
} else {
wcscpy(linenum_str, L"(none)");
Log_Status_Ex_Session(
MSG_CONF_READ, 0, status, linenum_str, session_id);
}
}
if (! NT_SUCCESS(status)) {
Pool_Delete(pool);
return status;
}
//
// read data from the file
//
data.pool = pool;
List_Init(&data.sections);
#ifdef USE_CONF_MAP
map_init(&data.sections_map, data.pool);
data.sections_map.func_key_size = NULL;
data.sections_map.func_match_key = &str_map_match;
data.sections_map.func_hash_key = &str_map_hash;
map_resize(&data.sections_map, 16); // prepare some buckets for better performance
#endif
data.home = path_home;
if (path_home == 2)
data.path = Mem_AllocStringEx(data.pool, path, TRUE);
else
data.path = NULL;
data.use_count = 0;
if (stream) {
status = Stream_Read_BOM(stream, &data.encoding);
linenum = 1;
while (NT_SUCCESS(status))
status = Conf_Read_Sections(stream, &data, &linenum);
if (status == STATUS_END_OF_FILE)
status = STATUS_SUCCESS;
}
if (stream) Stream_Close(stream);
//
// read (Home Path)\Templates.ini
//
if (NT_SUCCESS(status)) {
RtlStringCbPrintfW(path, path_len, path_templates, Driver_HomePathDos);
status = Stream_Open(
&stream, path,
FILE_GENERIC_READ, 0, FILE_SHARE_READ, FILE_OPEN, 0);
if (! NT_SUCCESS(status)) {
Log_Status_Ex_Session(
MSG_CONF_NO_TMPL_FILE, 0, status, NULL, session_id);
} else {
status = Stream_Read_BOM(stream, NULL);
linenum = 1 + CONF_TMPL_LINE_BASE;
while (NT_SUCCESS(status))
status = Conf_Read_Sections(stream, &data, &linenum);
if (status == STATUS_END_OF_FILE)
status = STATUS_SUCCESS;
Stream_Close(stream);
linenum -= CONF_TMPL_LINE_BASE;
if (! NT_SUCCESS(status))
Log_Msg_Session(MSG_CONF_BAD_TMPL_FILE, 0, NULL, session_id);
}
}
//
// merge templates
//
if (NT_SUCCESS(status)) {
status = Conf_Merge_Templates(&data, session_id);
linenum = 0;
}
//
// if read successfully, replace existing configuration
//
if (NT_SUCCESS(status)) {
BOOLEAN done = FALSE;
while (! done) {
KIRQL irql;
KeRaiseIrql(APC_LEVEL, &irql);
ExAcquireResourceExclusiveLite(Conf_Lock, TRUE);
if (Conf_Data.use_count == 0) {
pool = Conf_Data.pool;
memcpy(&Conf_Data, &data, sizeof(CONF_DATA));
done = TRUE;
}
ExReleaseResourceLite(Conf_Lock);
KeLowerIrql(irql);
if (! done)
ZwYieldExecution();
}
}
Mem_Free(path, path_len);
if (pool)
Pool_Delete(pool); // may be either data.pool or old Conf_Data.pool
//
// Possible error values through Conf_Read_* functions:
//
// STATUS_BUFFER_OVERFLOW (80000005) line too long
// STATUS_TOO_MANY_COMMANDS (C00000C1) too many lines in file
// STATUS_INVALID_PARAMETER (C000000D) syntax error
//
if (! NT_SUCCESS(status)) {
RtlStringCbPrintfW(linenum_str, sizeof(linenum_str), L"%d", linenum);
//DbgPrint("Conf error %X at line %d (%S)\n", status, linenum, linenum_str);
if (status == STATUS_BUFFER_OVERFLOW) {
Log_Msg_Session(
MSG_CONF_LINE_TOO_LONG, linenum_str, NULL, session_id);
} else if (status == STATUS_TOO_MANY_COMMANDS) {
Log_Msg_Session(
MSG_CONF_FILE_TOO_LONG, linenum_str, NULL, session_id);
} else if (status == STATUS_INVALID_PARAMETER) {
Log_Msg_Session(
MSG_CONF_SYNTAX_ERROR, linenum_str, NULL, session_id);
} else {
Log_Status_Ex_Session(
MSG_CONF_READ, 0, status, linenum_str, session_id);
}
}
return status;
}
//---------------------------------------------------------------------------
// Conf_Read_Sections
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Read_Sections(
STREAM *stream, CONF_DATA *data, int *linenum)
{
const int line_len = (CONF_LINE_LEN + 2) * sizeof(WCHAR);
NTSTATUS status;
WCHAR *line;
WCHAR *ptr;
CONF_SECTION *section;
line = Mem_Alloc(data->pool, line_len);
if (! line)
return STATUS_INSUFFICIENT_RESOURCES;
status = Conf_Read_Line(stream, line, linenum);
//DbgPrint("Conf_Read_Line (%d/%X) --> %S\n", *linenum, status, line);
while (NT_SUCCESS(status)) {
//
// extract the section name from the section name
//
if (line[0] != L'[') {
status = STATUS_INVALID_PARAMETER;
break;
}
ptr = &line[1];
while (*ptr && *ptr != L']')
++ptr;
if (*ptr != L']') {
status = STATUS_INVALID_PARAMETER;
break;
}
*ptr = L'\0';
if (_wcsnicmp(&line[1], Conf_UserSettings_, 13) == 0) {
if (! line[14]) {
status = STATUS_INVALID_PARAMETER;
break;
}
} else if (_wcsnicmp(&line[1], Conf_Template_, 9) == 0) {
if (! line[10]) {
status = STATUS_INVALID_PARAMETER;
break;
}
} else if (! Box_IsValidName(&line[1])) {
status = STATUS_INVALID_PARAMETER;
break;
}
//
// find an existing section by that name or create a new one
//
#ifdef USE_CONF_MAP
section = map_get(&data->sections_map, &line[1]);
#else
section = List_Head(&data->sections);
while (section) {
if (_wcsicmp(section->name, &line[1]) == 0)
break;
section = List_Next(section);
}
#endif
if (! section) {
section = Mem_Alloc(data->pool, sizeof(CONF_SECTION));
if (! section) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
if ((*linenum) >= CONF_TMPL_LINE_BASE)
section->from_template = TRUE;
else
section->from_template = FALSE;
section->name = Mem_AllocString(data->pool, &line[1]);
if (! section->name) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
List_Init(&section->settings);
#ifdef USE_CONF_MAP
map_init(&section->settings_map, data->pool);
section->settings_map.func_key_size = NULL;
section->settings_map.func_match_key = &str_map_match;
section->settings_map.func_hash_key = &str_map_hash;
map_resize(&section->settings_map, 16); // prepare some buckets for better performance
#endif
List_Insert_After(&data->sections, NULL, section);
#ifdef USE_CONF_MAP
if(map_insert(&data->sections_map, section->name, section, 0) == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
#endif
}
// read settings for this section
status = Conf_Read_Settings(stream, data, section, line, linenum);
}
Mem_Free(line, line_len);
return status;
}
//---------------------------------------------------------------------------
// Conf_Read_Settings
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Read_Settings(
STREAM *stream, CONF_DATA *data, CONF_SECTION *section,
WCHAR *line, int *linenum)
{
NTSTATUS status;
WCHAR *ptr;
WCHAR *value;
CONF_SETTING *setting;
while (1) {
status = Conf_Read_Line(stream, line, linenum);
if (! NT_SUCCESS(status))
break;
if (line[0] == L'[' || line[0] == L']')
break;
// parse setting name=value
ptr = wcschr(line, L'=');
if ((! ptr) || ptr == line) {
status = STATUS_INVALID_PARAMETER;
break;
}
value = &ptr[1];
// eliminate trailing whitespace in the setting name
while (ptr > line) {
--ptr;
if (*ptr > 32) {
++ptr;
break;
}
}
*ptr = L'\0';
// eliminate leading and trailing whitespace in value
while (*value <= 32) {
if (! (*value))
break;
++value;
}
if (*value == L'\0') {
status = STATUS_INVALID_PARAMETER;
break;
}
ptr = value + wcslen(value);
while (ptr > value) {
--ptr;
if (*ptr > 32) {
++ptr;
break;
}
}
*ptr = L'\0';
//
// add the new setting
//
setting = Mem_Alloc(data->pool, sizeof(CONF_SETTING));
if (! setting) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
if ((*linenum) >= CONF_TMPL_LINE_BASE)
setting->from_template = TRUE;
else
setting->from_template = FALSE;
setting->template_handled = FALSE;
setting->name = Mem_AllocString(data->pool, line);
if (! setting->name) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
setting->value = Mem_AllocString(data->pool, value);
if (! setting->value) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
List_Insert_After(&section->settings, NULL, setting);
#ifdef USE_CONF_MAP
if(map_append(&section->settings_map, setting->name, setting, 0) == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
#endif
}
return status;
}
//---------------------------------------------------------------------------
// Conf_Read_Line
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Read_Line(STREAM *stream, WCHAR *line, int *linenum)
{
NTSTATUS status;
WCHAR *ptr;
USHORT ch;
while (1) {
// skip leading control and whitespace characters
while (1) {
status = Stream_Read_Wchar(stream, &ch);
if ((! NT_SUCCESS(status)) || (ch > 32 && ch < 0xFE00))
break;
if (ch == L'\r')
continue;
if (ch == L'\n') {
ULONG numlines = (++(*linenum));
if (numlines >= CONF_TMPL_LINE_BASE)
numlines -= CONF_TMPL_LINE_BASE;
if (numlines > CONF_MAX_LINES) {
status = STATUS_TOO_MANY_COMMANDS;
break;
}
}
}
if (! NT_SUCCESS(status)) {
*line = L'\0';
break;
}
// read characters until hitting the newline mark
ptr = line;
while (1) {
*ptr = ch;
++ptr;
if (ptr - line == CONF_LINE_LEN)
status = STATUS_BUFFER_OVERFLOW;
else
status = Stream_Read_Wchar(stream, &ch);
if ((! NT_SUCCESS(status)) || ch == L'\n' || ch == L'\r')
break;
}
// remove all trailing control and whitespace characters
while (ptr > line) {
--ptr;
if (*ptr > 32) {
++ptr;
break;
}
}
*ptr = L'\0';
// don't report end-of-file if we have data to return
if (ptr > line && status == STATUS_END_OF_FILE)
status = STATUS_SUCCESS;
// if we are about to successfully return a comment line,
// then discard the line and restart from the top
if (status == STATUS_SUCCESS && *line == L'#')
continue;
break;
}
return status;
}
//---------------------------------------------------------------------------
// Conf_Get_Section
//---------------------------------------------------------------------------
_FX CONF_SECTION* Conf_Get_Section(
CONF_DATA* data, const WCHAR* section_name)
{
#ifdef USE_CONF_MAP
//
// lookup the template section in the hash map
//
return map_get(&data->sections_map, section_name);
#else
//
// scan for a matching template section
//
CONF_SECTION* section = List_Head(&data->sections);
while (section) {
if (_wcsicmp(section->name, section_name) == 0) {
break;
}
section = List_Next(section);
}
return section;
#endif
}
//---------------------------------------------------------------------------
// Conf_Merge_Templates
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Merge_Templates(CONF_DATA *data, ULONG session_id)
{
NTSTATUS status;
CONF_SECTION *sandbox;
CONF_SETTING *setting;
//
// first handle the global section
//
CONF_SECTION* global = Conf_Get_Section(data, Conf_GlobalSettings);
if (global) {
status = Conf_Merge_Global(data, session_id, global);
if (!NT_SUCCESS(status))
return status;
}
//
// second handle the default templates
//
global = Conf_Get_Section(data, Conf_DefaultTemplates);
if (global) {
status = Conf_Merge_Global(data, session_id, global);
if (!NT_SUCCESS(status))
return status;
}
//
// scan sections to find a sandbox section
//
sandbox = List_Head(&data->sections);
while (sandbox) {
CONF_SECTION *next_sandbox = List_Next(sandbox);
//
// break once the template section starts
//
if (sandbox->from_template) {
// we can break because template sections come after
// all non-template sections
break;
}
//
// skip the global section, skip any local template sections and user settings sections
//
if (_wcsicmp(sandbox->name, Conf_GlobalSettings) == 0 ||
_wcsnicmp(sandbox->name, Conf_Template_, 9) == 0 || // Template_ or Template_Local_
_wcsnicmp(sandbox->name, Conf_UserSettings_, 13) == 0) {
sandbox = next_sandbox;
continue;
}
#ifdef USE_CONF_MAP
//
// use a keyed iterator to quickly go through all Template=Xxx settings
//
map_iter_t iter2 = map_key_iter(&sandbox->settings_map, Conf_Template);
while (map_next(&sandbox->settings_map, &iter2)) {
setting = iter2.value;
#else
//
// scan the section for a Template=Xxx setting
//
setting = List_Head(&sandbox->settings);
while (setting) {
if (_wcsicmp(setting->name, Conf_Template) != 0) {
setting = List_Next(setting);
continue;
}
#endif
if (setting->template_handled) {
#ifndef USE_CONF_MAP
setting = List_Next(setting);
#endif
continue;
}
//
// merge the template into the sandbox section
//
status = Conf_Merge_Template(
data, session_id, setting->value, sandbox, NULL);
if (! NT_SUCCESS(status))
return status;
setting->template_handled = TRUE;
#ifndef USE_CONF_MAP
//
// advance to next setting
//
setting = List_Head(&sandbox->settings);
#endif
}
//
// advance to next section
//
sandbox = next_sandbox;
}
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// Conf_Merge_Global
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Merge_Global(
CONF_DATA *data, ULONG session_id,
CONF_SECTION *global)
{
NTSTATUS status;
CONF_SECTION *sandbox;
CONF_SETTING *setting;
//
// scan the section for a Template=Xxx setting
//
setting = List_Head(&global->settings);
while (setting) {
if (_wcsicmp(setting->name, Conf_Template) != 0) {
setting = List_Next(setting);
continue;
}
//
// scan sections to find a sandbox section
//
sandbox = List_Head(&data->sections);
while (sandbox) {
CONF_SECTION *next_sandbox = List_Next(sandbox);
//
// break once the template section starts
//
if (sandbox->from_template) {
// we can break because template sections come after
// all non-template sections
break;
}
//
// skip the global section, any template sections and user settings sections
//
if (_wcsicmp(sandbox->name, Conf_GlobalSettings) == 0 ||
_wcsnicmp(sandbox->name, Conf_Template_, 9) == 0 ||
_wcsnicmp(sandbox->name, Conf_UserSettings_, 13) == 0) {
sandbox = next_sandbox;
continue;
}
//
// merge the template into the sandbox section
//
status = Conf_Merge_Template(
data, session_id, setting->value, sandbox, L"GlobalSettings");
if (! NT_SUCCESS(status))
return status;
//
// advance to next section
//
sandbox = next_sandbox;
}
//
// advance to next setting
//
setting = List_Next(setting);
}
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// Conf_Merge_Template
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Merge_Template(
CONF_DATA *data, ULONG session_id,
const WCHAR *tmpl_name, CONF_SECTION *section, const WCHAR* name)
{
CONF_SECTION *tmpl = NULL;
WCHAR section_name[130]; // 128 + 2 // max regular section length is 64
if (wcslen(tmpl_name) < 119) { // 128 - wcslen(Conf_Template_)
wcscpy(section_name, Conf_Template_);
wcscat(section_name, tmpl_name);
tmpl = Conf_Get_Section(data, section_name);
}
//
// copy settings from template section into sandbox section
//
if (tmpl) {
CONF_SETTING *oset, *nset;
oset = List_Head(&tmpl->settings);
while (oset) {
if (_wcsnicmp(oset->name, Conf_Tmpl, 5) == 0) {
oset = List_Next(oset);
continue;
}
nset = Mem_Alloc(data->pool, sizeof(CONF_SETTING));
nset->from_template = TRUE;
nset->template_handled = FALSE;
if (! nset)
return STATUS_INSUFFICIENT_RESOURCES;
nset->name = Mem_AllocString(data->pool, oset->name);
if (! nset->name)
return STATUS_INSUFFICIENT_RESOURCES;
nset->value = Mem_AllocString(data->pool, oset->value);
if (! nset->value)
return STATUS_INSUFFICIENT_RESOURCES;
List_Insert_After(&section->settings, NULL, nset);
#ifdef USE_CONF_MAP
if(map_append(&section->settings_map, nset->name, nset, 0) == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
#endif
oset = List_Next(oset);
}
} else {
Log_Msg_Session(MSG_CONF_MISSING_TMPL,
name ? name : section->name, tmpl_name, session_id);
}
return STATUS_SUCCESS;
}
//---------------------------------------------------------------------------
// Conf_Get_Helper
//---------------------------------------------------------------------------
_FX const WCHAR *Conf_Get_Helper(
const WCHAR *section_name, const WCHAR *setting_name,
ULONG *index, BOOLEAN skip_tmpl)
{
WCHAR *value;
CONF_SECTION *section;
CONF_SETTING *setting;
value = NULL;
#ifdef USE_CONF_MAP
//
// lookup the section in the hash map
//
section = map_get(&Conf_Data.sections_map, section_name);
#else
section = List_Head(&Conf_Data.sections);
while (section) {
//DbgPrint(" Examining section at %X name %S (looking for %S)\n", section, section->name, section_name);
if (_wcsicmp(section->name, section_name) == 0)
break;
section = List_Next(section);
}
#endif
if (skip_tmpl && section && section->from_template)
section = NULL;
if (section) {
#ifdef USE_CONF_MAP
//
// use a keyed iterator to quickly go through all matching settings
//
map_iter_t iter2 = map_key_iter(&section->settings_map, setting_name);
while (map_next(&section->settings_map, &iter2)) {
setting = iter2.value;
#else
setting = List_Head(&section->settings);
while (setting) {
//DbgPrint(" Examining setting at %X name %S (looking for %S)\n", setting, setting->name, setting_name);
#endif
if (skip_tmpl && setting->from_template) {
// we can break because template settings come after
// all non-template settings
break;
}
#ifndef USE_CONF_MAP
if (_wcsicmp(setting->name, setting_name) == 0) {
#endif
if (*index == 0) {
value = setting->value;
break;
}
--(*index);
#ifndef USE_CONF_MAP
}
setting = List_Next(setting);
#endif
}
}
return value;
}
//---------------------------------------------------------------------------
// Conf_Get_Section_Name
//---------------------------------------------------------------------------
_FX const WCHAR *Conf_Get_Section_Name(ULONG index, BOOLEAN skip_tmpl)
{
WCHAR *value;
CONF_SECTION *section;
value = NULL;
section = List_Head(&Conf_Data.sections);
while (section) {
CONF_SECTION *next_section = List_Next(section);
if (_wcsicmp(section->name, Conf_GlobalSettings) == 0) {
section = next_section;
continue;
}
if (skip_tmpl && section->from_template) {
// we can break because template sections come after
// all non-template sections
break;
}
if (index == 0) {
value = section->name;
break;
}
--index;
section = next_section;
}
return value;
}
//---------------------------------------------------------------------------
// Conf_Get_Setting_Name
//---------------------------------------------------------------------------
_FX const WCHAR *Conf_Get_Setting_Name(
const WCHAR *section_name, ULONG index, BOOLEAN skip_tmpl)
{
WCHAR *value;
CONF_SECTION *section;
CONF_SETTING *setting, *setting2;
BOOLEAN dup;
value = NULL;
#ifdef USE_CONF_MAP
//
// lookup the section in the hash map
//
section = map_get(&Conf_Data.sections_map, section_name);
#else
section = List_Head(&Conf_Data.sections);
while (section) {
if (_wcsicmp(section->name, section_name) == 0)
break;
section = List_Next(section);
}
#endif
if (skip_tmpl && section && section->from_template)
section = NULL;
if (section) {
setting = List_Head(&section->settings);
while (setting) {
if (skip_tmpl && setting->from_template) {
// we can break because template settings come after
// all non-template settings
break;
}
//
// check if we already processed this name
//
dup = FALSE;
setting2 = List_Head(&section->settings);
while (setting2 && setting2 != setting) {
if (_wcsicmp(setting2->name, setting->name) == 0) {
dup = TRUE;
break;
} else
setting2 = List_Next(setting2);
}
if (! dup) {
if (index == 0) {
value = setting->name;
break;
} else
--index;
}
setting = List_Next(setting);
}
}
return value;
}
//---------------------------------------------------------------------------
// Conf_Get
//---------------------------------------------------------------------------
_FX const WCHAR *Conf_Get(
const WCHAR *section, const WCHAR *setting, ULONG index)
{
const WCHAR *value;
BOOLEAN have_section;
BOOLEAN have_setting;
BOOLEAN check_global;
BOOLEAN skip_tmpl;
KIRQL irql;
value = NULL;
have_section = (section && section[0]);
have_setting = (setting && setting[0]);
skip_tmpl = ((index & CONF_GET_NO_TEMPLS) != 0);
KeRaiseIrql(APC_LEVEL, &irql);
ExAcquireResourceSharedLite(Conf_Lock, TRUE);
if ((! have_section) && have_setting &&
_wcsicmp(setting, L"IniLocation") == 0) {
// return "H" if configuration file was found in the Sandboxie
// home directory, or "W" if it was found in Windows directory
if (Conf_Data.path) value = Conf_Data.path;
else value = (Conf_Data.home) ? Conf_H : Conf_W;
} else if ((!have_section) && have_setting &&
_wcsicmp(setting, L"IniEncoding") == 0) {
// return "U" if configuration file was Unicode encoded,
// or "8" if it was UTF-8 encoded
value = (Conf_Data.encoding == 1) ? Conf_UTF8 : Conf_Unicode;
}
else if (have_setting) {
check_global = ((index & CONF_GET_NO_GLOBAL) == 0);
index &= 0xFFFF;
if (section)
value = Conf_Get_Helper(section, setting, &index, skip_tmpl);
//
// when no value has been found for the given section
// try getting it from the global section
//
if ((! value) && check_global) {
value = Conf_Get_Helper(
Conf_GlobalSettings, setting, &index, skip_tmpl);
}
} else if (have_section && (! have_setting)) {
value = Conf_Get_Setting_Name(section, index & 0xFFFF, skip_tmpl);
} else if ((! have_section) && (! have_setting)) {
value = Conf_Get_Section_Name(index & 0xFFFF, skip_tmpl);
}
ExReleaseResourceLite(Conf_Lock);
KeLowerIrql(irql);
return value;
}
//---------------------------------------------------------------------------
// Conf_Get_Boolean
//---------------------------------------------------------------------------
_FX BOOLEAN Conf_Get_Boolean(
const WCHAR *section, const WCHAR *setting, ULONG index, BOOLEAN def)
{
const WCHAR *value;
BOOLEAN retval;
Conf_AdjustUseCount(TRUE);
value = Conf_Get(section, setting, index);
retval = def;
if (value) {
if (*value == 'y' || *value == 'Y')
retval = TRUE;
else if (*value == 'n' || *value == 'N')
retval = FALSE;
}
Conf_AdjustUseCount(FALSE);
return retval;
}
//---------------------------------------------------------------------------
// Conf_Get_Number
//---------------------------------------------------------------------------
_FX ULONG Conf_Get_Number(
const WCHAR *section, const WCHAR *setting, ULONG index, ULONG def)
{
const WCHAR *value;
ULONG retval;
Conf_AdjustUseCount(TRUE);
value = Conf_Get(section, setting, index);
retval = def;
if (value) {
NTSTATUS status;
UNICODE_STRING uni;
RtlInitUnicodeString(&uni, value);
status = RtlUnicodeStringToInteger(&uni, 10, &retval);
if (! NT_SUCCESS(status))
retval = def;
}
Conf_AdjustUseCount(FALSE);
return retval;
}
//---------------------------------------------------------------------------
// Conf_IsValidBox
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_IsValidBox(const WCHAR *section_name)
{
CONF_SECTION *section;
NTSTATUS status;
KIRQL irql;
if ( _wcsicmp(section_name, Conf_GlobalSettings) == 0
|| _wcsicmp(section_name, Conf_TemplateSettings) == 0
|| _wcsnicmp(section_name, Conf_Template_, 9) == 0
|| _wcsnicmp(section_name, Conf_UserSettings_, 13) == 0) {
status = STATUS_OBJECT_TYPE_MISMATCH;
} else {
KeRaiseIrql(APC_LEVEL, &irql);
ExAcquireResourceSharedLite(Conf_Lock, TRUE);
section = List_Head(&Conf_Data.sections);
while (section) {
if (_wcsicmp(section->name, section_name) == 0)
break;
section = List_Next(section);
}
if (! section)
status = STATUS_OBJECT_NAME_NOT_FOUND;
else if (section->from_template)
status = STATUS_OBJECT_TYPE_MISMATCH;
else
status = STATUS_SUCCESS;
ExReleaseResourceLite(Conf_Lock);
KeLowerIrql(irql);
}
return status;
}
//---------------------------------------------------------------------------
// Conf_Api_Reload
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Api_Reload(PROCESS *proc, ULONG64 *parms)
{
NTSTATUS status;
ULONG flags;
if (proc)
return STATUS_NOT_IMPLEMENTED;
flags = (ULONG)parms[2];
if (flags & SBIE_CONF_FLAG_RELOAD_CERT) {
status = MyValidateCertificate();
goto finish;
}
status = Conf_Read((ULONG)parms[1]);
if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
status == STATUS_OBJECT_PATH_NOT_FOUND) {
//
// if configuration file was removed, reset configuration
//
POOL *pool;
KIRQL irql;
KeRaiseIrql(APC_LEVEL, &irql);
ExAcquireResourceExclusiveLite(Conf_Lock, TRUE);
pool = Conf_Data.pool;
Conf_Data.pool = NULL;
List_Init(&Conf_Data.sections);
#ifdef USE_CONF_MAP
map_init(&Conf_Data.sections_map, NULL);
Conf_Data.sections_map.func_key_size = NULL;
Conf_Data.sections_map.func_match_key = &str_map_match;
Conf_Data.sections_map.func_hash_key = &str_map_hash;
map_resize(&Conf_Data.sections_map, 16); // prepare some buckets for better performance
#endif
Conf_Data.home = FALSE;
Conf_Data.path = NULL;
Conf_Data.encoding = 0;
ExReleaseResourceLite(Conf_Lock);
KeLowerIrql(irql);
if (pool)
Pool_Delete(pool);
status = STATUS_SUCCESS;
}
//
// Check the reconfigure drier flag and if its set, load/unload the components accordingly
//
if (flags & SBIE_CONF_FLAG_RECONFIGURE) {
static volatile ULONG reconf_lock = 0;
if (InterlockedCompareExchange(&reconf_lock, 1, 0) != 0) {
status = STATUS_OPERATION_IN_PROGRESS;
goto finish; // don't do anything is a reconfiguration is already in progress
}
BOOLEAN wpf_enabled = Conf_Get_Boolean(NULL, L"NetworkEnableWFP", 0, FALSE);
extern BOOLEAN WFP_Enabled;
if (WFP_Enabled != wpf_enabled) {
if (wpf_enabled) {
extern BOOLEAN WFP_Load(void);
WFP_Load();
}
else {
extern void WFP_Unload(void);
WFP_Unload();
}
}
BOOLEAN obj_filter_enabled = Conf_Get_Boolean(NULL, L"EnableObjectFiltering", 0, TRUE);
if (Obj_CallbackInstalled != obj_filter_enabled && Driver_OsVersion > DRIVER_WINDOWS_VISTA) {
if (obj_filter_enabled) {
Obj_Load_Filter();
}
else {
Obj_Unload_Filter();
}
}
void Syscall_Update_Lockdown();
Syscall_Update_Lockdown();
/*
#ifdef HOOK_WIN32K
// must be windows 10 or later
if (Driver_OsBuild >= 14393) {
extern ULONG Syscall_MaxIndex32;
if (Conf_Get_Boolean(NULL, L"EnableWin32kHooks", 0, FALSE) && Syscall_MaxIndex32 == 0) {
if(Syscall_Init_List32()){
Syscall_Init_Table32();
}
}
}
#endif
*/
InterlockedExchange(&reconf_lock, 0);
}
//
// notify service about setting change
//
ULONG process_id = (ULONG)PsGetCurrentProcessId();
Api_SendServiceMessage(SVC_CONFIG_UPDATED, sizeof(process_id), &process_id);
finish:
return status;
}
//---------------------------------------------------------------------------
// Conf_Api_Query
//---------------------------------------------------------------------------
_FX NTSTATUS Conf_Api_Query(PROCESS *proc, ULONG64 *parms)
{
NTSTATUS status;
WCHAR *parm;
ULONG *parm2;
WCHAR boxname[70];
WCHAR setting[70];
ULONG index;
const WCHAR *value1;
WCHAR *value2;
//
// prepare parameters
//
// parms[1] --> WCHAR [66] SectionName
memzero(boxname, sizeof(boxname));
if (proc)
wcscpy(boxname, proc->box->name);
else {
parm = (WCHAR *)parms[1];
if (parm) {
ProbeForRead(parm, sizeof(WCHAR) * 64, sizeof(WCHAR));
if (parm[0])
wcsncpy(boxname, parm, 64);
}
}
// parms[2] --> WCHAR [66] SettingName
memzero(setting, sizeof(setting));
parm = (WCHAR *)parms[2];
if (parm) {
ProbeForRead(parm, sizeof(WCHAR) * 64, sizeof(WCHAR));
if (parm[0])
wcsncpy(setting, parm, 64);
}
// parms[3] --> ULONG SettingIndex
index = 0;
parm2 = (ULONG *)parms[3];
if (parm2) {
ProbeForRead(parm2, sizeof(ULONG), sizeof(ULONG));
index = *parm2;
if ((index & 0xFFFF) > 1000)
return STATUS_INVALID_PARAMETER;
} else
return STATUS_INVALID_PARAMETER;
//
// get value
//
Conf_AdjustUseCount(TRUE);
if ((setting && setting[0] == L'%') || (index & CONF_JUST_EXPAND))
value1 = setting; // shortcut to expand a variable
else
value1 = Conf_Get(boxname, setting, index);
if (! value1) {
status = STATUS_RESOURCE_NAME_NOT_FOUND;
goto release_and_return;
}
if (index & CONF_GET_NO_EXPAND)
value2 = (WCHAR *)value1;
else {
// expand value. if caller is sandboxed, use its BOX (with its
// expand_args) for that. otherwise, create a temporary BOX
if (proc)
value2 = Conf_Expand(proc->box->expand_args, value1, setting);
else {
CONF_EXPAND_ARGS *expand_args = Mem_Alloc(Driver_Pool, sizeof(CONF_EXPAND_ARGS));
if (! expand_args) {
status = STATUS_UNSUCCESSFUL;
goto release_and_return;
}
expand_args->pool = Driver_Pool;
expand_args->sandbox = boxname;
UNICODE_STRING SidString;
ULONG SessionId;
status = Process_GetSidStringAndSessionId(NtCurrentProcess(), NULL, &SidString, &SessionId);
if (!NT_SUCCESS(status)) {
Mem_Free(expand_args, sizeof(CONF_EXPAND_ARGS));
status = STATUS_UNSUCCESSFUL;
goto release_and_return;
}
expand_args->sid = SidString.Buffer;
expand_args->session = &SessionId;
value2 = Conf_Expand(expand_args, value1, setting);
RtlFreeUnicodeString(&SidString);
Mem_Free(expand_args, sizeof(CONF_EXPAND_ARGS));
}
if (! value2) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto release_and_return;
}
}
// write value into user buffer Output
// parms[4] --> user buffer Output
__try {
UNICODE_STRING64 *user_uni = (UNICODE_STRING64 *)parms[4];
ULONG len = (wcslen(value2) + 1) * sizeof(WCHAR);
Api_CopyStringToUser(user_uni, value2, len);
status = STATUS_SUCCESS;
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
if (value2 != value1)
Mem_FreeString(value2);
release_and_return:
Conf_AdjustUseCount(FALSE);
return status;
}
//---------------------------------------------------------------------------
// Conf_Init
//---------------------------------------------------------------------------
_FX BOOLEAN Conf_Init(void)
{
Conf_Data.pool = NULL;
List_Init(&Conf_Data.sections);
#ifdef USE_CONF_MAP
map_init(&Conf_Data.sections_map, NULL);
Conf_Data.sections_map.func_key_size = NULL;
Conf_Data.sections_map.func_match_key = &str_map_match;
Conf_Data.sections_map.func_hash_key = &str_map_hash;
#endif
Conf_Data.home = FALSE;
Conf_Data.path = NULL;
Conf_Data.encoding = 0;
if (! Mem_GetLockResource(&Conf_Lock, TRUE))
return FALSE;
if (! Conf_Init_User())
return FALSE;
Conf_Read(-1);
//
// set API functions
//
Api_SetFunction(API_RELOAD_CONF, Conf_Api_Reload);
Api_SetFunction(API_QUERY_CONF, Conf_Api_Query);
return TRUE;
}
//---------------------------------------------------------------------------
// Conf_Unload
//---------------------------------------------------------------------------
_FX void Conf_Unload(void)
{
Conf_Unload_User();
if (Conf_Data.pool) {
Pool_Delete(Conf_Data.pool);
Conf_Data.pool = NULL;
}
Mem_FreeLockResource(&Conf_Lock);
}