/* Copyright (c) Mark Harmstone 2016-17 * * This file is part of WinBtrfs. * * WinBtrfs is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public Licence as published by * the Free Software Foundation, either version 3 of the Licence, or * (at your option) any later version. * * WinBtrfs 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 Lesser General Public Licence for more details. * * You should have received a copy of the GNU Lesser General Public Licence * along with WinBtrfs. If not, see . */ #include "btrfs_drv.h" #include "zstd/zstd.h" extern UNICODE_STRING log_device, log_file, registry_path; extern LIST_ENTRY uid_map_list, gid_map_list; extern ERESOURCE mapping_lock; #ifdef _DEBUG extern HANDLE log_handle; extern ERESOURCE log_lock; extern PFILE_OBJECT comfo; extern PDEVICE_OBJECT comdo; #endif WORK_QUEUE_ITEM wqi; static const WCHAR option_mounted[] = L"Mounted"; NTSTATUS registry_load_volume_options(device_extension* Vcb) { BTRFS_UUID* uuid = &Vcb->superblock.uuid; mount_options* options = &Vcb->options; UNICODE_STRING path, ignoreus, compressus, compressforceus, compresstypeus, readonlyus, zliblevelus, flushintervalus, maxinlineus, subvolidus, skipbalanceus, nobarrierus, notrimus, clearcacheus, allowdegradedus, zstdlevelus, norootdirus; OBJECT_ATTRIBUTES oa; NTSTATUS Status; ULONG i, j, kvfilen, index, retlen; KEY_VALUE_FULL_INFORMATION* kvfi = NULL; HANDLE h; options->compress = mount_compress; options->compress_force = mount_compress_force; options->compress_type = mount_compress_type > BTRFS_COMPRESSION_ZSTD ? 0 : mount_compress_type; options->readonly = mount_readonly; options->zlib_level = mount_zlib_level; options->zstd_level = mount_zstd_level; options->flush_interval = mount_flush_interval; options->max_inline = min(mount_max_inline, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1); options->skip_balance = mount_skip_balance; options->no_barrier = mount_no_barrier; options->no_trim = mount_no_trim; options->clear_cache = mount_clear_cache; options->allow_degraded = mount_allow_degraded; options->subvol_id = 0; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)); kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); Status = STATUS_INSUFFICIENT_RESOURCES; goto end; } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = STATUS_SUCCESS; goto end; } else if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); goto end; } index = 0; RtlInitUnicodeString(&ignoreus, L"Ignore"); RtlInitUnicodeString(&compressus, L"Compress"); RtlInitUnicodeString(&compressforceus, L"CompressForce"); RtlInitUnicodeString(&compresstypeus, L"CompressType"); RtlInitUnicodeString(&readonlyus, L"Readonly"); RtlInitUnicodeString(&zliblevelus, L"ZlibLevel"); RtlInitUnicodeString(&flushintervalus, L"FlushInterval"); RtlInitUnicodeString(&maxinlineus, L"MaxInline"); RtlInitUnicodeString(&subvolidus, L"SubvolId"); RtlInitUnicodeString(&skipbalanceus, L"SkipBalance"); RtlInitUnicodeString(&nobarrierus, L"NoBarrier"); RtlInitUnicodeString(¬rimus, L"NoTrim"); RtlInitUnicodeString(&clearcacheus, L"ClearCache"); RtlInitUnicodeString(&allowdegradedus, L"AllowDegraded"); RtlInitUnicodeString(&zstdlevelus, L"ZstdLevel"); RtlInitUnicodeString(&norootdirus, L"NoRootDir"); do { Status = ZwEnumerateValueKey(h, index, KeyValueFullInformation, kvfi, kvfilen, &retlen); index++; if (NT_SUCCESS(Status)) { UNICODE_STRING us; us.Length = us.MaximumLength = (USHORT)kvfi->NameLength; us.Buffer = kvfi->Name; if (FsRtlAreNamesEqual(&ignoreus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->ignore = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compressus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compressforceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress_force = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&compresstypeus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->compress_type = (uint8_t)(*val > BTRFS_COMPRESSION_ZSTD ? 0 : *val); } else if (FsRtlAreNamesEqual(&readonlyus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->readonly = *val != 0 ? true : false; } else if (FsRtlAreNamesEqual(&zliblevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->zlib_level = *val; } else if (FsRtlAreNamesEqual(&flushintervalus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->flush_interval = *val; } else if (FsRtlAreNamesEqual(&maxinlineus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->max_inline = min(*val, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1); } else if (FsRtlAreNamesEqual(&subvolidus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_QWORD) { uint64_t* val = (uint64_t*)((uint8_t*)kvfi + kvfi->DataOffset); options->subvol_id = *val; } else if (FsRtlAreNamesEqual(&skipbalanceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->skip_balance = *val; } else if (FsRtlAreNamesEqual(&nobarrierus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_barrier = *val; } else if (FsRtlAreNamesEqual(¬rimus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_trim = *val; } else if (FsRtlAreNamesEqual(&clearcacheus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->clear_cache = *val; } else if (FsRtlAreNamesEqual(&allowdegradedus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->allow_degraded = *val; } else if (FsRtlAreNamesEqual(&zstdlevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->zstd_level = *val; } else if (FsRtlAreNamesEqual(&norootdirus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset); options->no_root_dir = *val; } } else if (Status != STATUS_NO_MORE_ENTRIES) { ERR("ZwEnumerateValueKey returned %08lx\n", Status); goto end2; } } while (NT_SUCCESS(Status)); if (!options->compress && options->compress_force) options->compress = true; if (options->zlib_level > 9) options->zlib_level = 9; if (options->zstd_level > (uint32_t)ZSTD_maxCLevel()) options->zstd_level = ZSTD_maxCLevel(); if (options->flush_interval == 0) options->flush_interval = mount_flush_interval; Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(path.Buffer); if (kvfi) ExFreePool(kvfi); return Status; } NTSTATUS registry_mark_volume_mounted(BTRFS_UUID* uuid) { UNICODE_STRING path, mountedus; ULONG i, j; NTSTATUS Status; OBJECT_ATTRIBUTES oa; HANDLE h; DWORD data; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_SET_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, NULL); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); goto end; } mountedus.Buffer = (WCHAR*)option_mounted; mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR); data = 1; Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); goto end2; } Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(path.Buffer); return Status; } static NTSTATUS registry_mark_volume_unmounted_path(PUNICODE_STRING path) { HANDLE h; OBJECT_ATTRIBUTES oa; NTSTATUS Status; ULONG index, kvbilen = sizeof(KEY_VALUE_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen; KEY_VALUE_BASIC_INFORMATION* kvbi; bool has_options = false; UNICODE_STRING mountedus; // If a volume key has any options in it, we set Mounted to 0 and return. Otherwise, // we delete the whole thing. kvbi = ExAllocatePoolWithTag(PagedPool, kvbilen, ALLOC_TAG); if (!kvbi) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } InitializeObjectAttributes(&oa, path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenKey(&h, KEY_QUERY_VALUE | KEY_SET_VALUE | DELETE, &oa); if (!NT_SUCCESS(Status)) { ERR("ZwOpenKey returned %08lx\n", Status); goto end; } index = 0; mountedus.Buffer = (WCHAR*)option_mounted; mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR); do { Status = ZwEnumerateValueKey(h, index, KeyValueBasicInformation, kvbi, kvbilen, &retlen); index++; if (NT_SUCCESS(Status)) { UNICODE_STRING us; us.Length = us.MaximumLength = (USHORT)kvbi->NameLength; us.Buffer = kvbi->Name; if (!FsRtlAreNamesEqual(&mountedus, &us, true, NULL)) { has_options = true; break; } } else if (Status != STATUS_NO_MORE_ENTRIES) { ERR("ZwEnumerateValueKey returned %08lx\n", Status); goto end2; } } while (NT_SUCCESS(Status)); if (has_options) { DWORD data = 0; Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); goto end2; } } else { Status = ZwDeleteKey(h); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteKey returned %08lx\n", Status); goto end2; } } Status = STATUS_SUCCESS; end2: ZwClose(h); end: ExFreePool(kvbi); return Status; } NTSTATUS registry_mark_volume_unmounted(BTRFS_UUID* uuid) { UNICODE_STRING path; NTSTATUS Status; ULONG i, j; path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR)); path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length); i = registry_path.Length / sizeof(WCHAR); path.Buffer[i] = '\\'; i++; for (j = 0; j < 16; j++) { path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4); path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF); i += 2; if (j == 3 || j == 5 || j == 7 || j == 9) { path.Buffer[i] = '-'; i++; } } Status = registry_mark_volume_unmounted_path(&path); if (!NT_SUCCESS(Status)) { ERR("registry_mark_volume_unmounted_path returned %08lx\n", Status); goto end; } Status = STATUS_SUCCESS; end: ExFreePool(path.Buffer); return Status; } #define is_hex(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) static bool is_uuid(ULONG namelen, WCHAR* name) { ULONG i; if (namelen != 36 * sizeof(WCHAR)) return false; for (i = 0; i < 36; i++) { if (i == 8 || i == 13 || i == 18 || i == 23) { if (name[i] != '-') return false; } else if (!is_hex(name[i])) return false; } return true; } typedef struct { UNICODE_STRING name; LIST_ENTRY list_entry; } key_name; static void reset_subkeys(HANDLE h, PUNICODE_STRING reg_path) { NTSTATUS Status; KEY_BASIC_INFORMATION* kbi; ULONG kbilen = sizeof(KEY_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen, index = 0; LIST_ENTRY key_names, *le; InitializeListHead(&key_names); kbi = ExAllocatePoolWithTag(PagedPool, kbilen, ALLOC_TAG); if (!kbi) { ERR("out of memory\n"); return; } do { Status = ZwEnumerateKey(h, index, KeyBasicInformation, kbi, kbilen, &retlen); index++; if (NT_SUCCESS(Status)) { key_name* kn; TRACE("key: %.*S\n", (int)(kbi->NameLength / sizeof(WCHAR)), kbi->Name); if (is_uuid(kbi->NameLength, kbi->Name)) { kn = ExAllocatePoolWithTag(PagedPool, sizeof(key_name), ALLOC_TAG); if (!kn) { ERR("out of memory\n"); goto end; } kn->name.Length = kn->name.MaximumLength = (USHORT)min(0xffff, kbi->NameLength); kn->name.Buffer = ExAllocatePoolWithTag(PagedPool, kn->name.MaximumLength, ALLOC_TAG); if (!kn->name.Buffer) { ERR("out of memory\n"); ExFreePool(kn); goto end; } RtlCopyMemory(kn->name.Buffer, kbi->Name, kn->name.Length); InsertTailList(&key_names, &kn->list_entry); } } else if (Status != STATUS_NO_MORE_ENTRIES) ERR("ZwEnumerateKey returned %08lx\n", Status); } while (NT_SUCCESS(Status)); le = key_names.Flink; while (le != &key_names) { key_name* kn = CONTAINING_RECORD(le, key_name, list_entry); UNICODE_STRING path; path.Length = path.MaximumLength = reg_path->Length + sizeof(WCHAR) + kn->name.Length; path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG); if (!path.Buffer) { ERR("out of memory\n"); goto end; } RtlCopyMemory(path.Buffer, reg_path->Buffer, reg_path->Length); path.Buffer[reg_path->Length / sizeof(WCHAR)] = '\\'; RtlCopyMemory(&path.Buffer[(reg_path->Length / sizeof(WCHAR)) + 1], kn->name.Buffer, kn->name.Length); Status = registry_mark_volume_unmounted_path(&path); if (!NT_SUCCESS(Status)) WARN("registry_mark_volume_unmounted_path returned %08lx\n", Status); ExFreePool(path.Buffer); le = le->Flink; } end: while (!IsListEmpty(&key_names)) { key_name* kn; le = RemoveHeadList(&key_names); kn = CONTAINING_RECORD(le, key_name, list_entry); if (kn->name.Buffer) ExFreePool(kn->name.Buffer); ExFreePool(kn); } ExFreePool(kbi); } static void read_mappings(PUNICODE_STRING regpath) { WCHAR* path; UNICODE_STRING us; HANDLE h; OBJECT_ATTRIBUTES oa; ULONG dispos; NTSTATUS Status; static const WCHAR mappings[] = L"\\Mappings"; while (!IsListEmpty(&uid_map_list)) { uid_map* um = CONTAINING_RECORD(RemoveHeadList(&uid_map_list), uid_map, listentry); if (um->sid) ExFreePool(um->sid); ExFreePool(um); } path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG); if (!path) { ERR("out of memory\n"); return; } RtlCopyMemory(path, regpath->Buffer, regpath->Length); RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR)); us.Buffer = path; us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR); InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); ExFreePool(path); return; } if (dispos == REG_OPENED_EXISTING_KEY) { KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, retlen, i; kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256; kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ExFreePool(path); ZwClose(h); return; } i = 0; do { Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen); if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { uint32_t val = 0; RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t))); TRACE("entry %lu = %.*S = %u\n", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val); add_user_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val); } i = i + 1; } while (Status != STATUS_NO_MORE_ENTRIES); ExFreePool(kvfi); } ZwClose(h); ExFreePool(path); } static void read_group_mappings(PUNICODE_STRING regpath) { WCHAR* path; UNICODE_STRING us; HANDLE h; OBJECT_ATTRIBUTES oa; ULONG dispos; NTSTATUS Status; static const WCHAR mappings[] = L"\\GroupMappings"; while (!IsListEmpty(&gid_map_list)) { gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry); if (gm->sid) ExFreePool(gm->sid); ExFreePool(gm); } path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG); if (!path) { ERR("out of memory\n"); return; } RtlCopyMemory(path, regpath->Buffer, regpath->Length); RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR)); us.Buffer = path; us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR); InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); ExFreePool(path); return; } ExFreePool(path); if (dispos == REG_OPENED_EXISTING_KEY) { KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, retlen, i; kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256; kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } i = 0; do { Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen); if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) { uint32_t val = 0; RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t))); TRACE("entry %lu = %.*S = %u\n", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val); add_group_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val); } i = i + 1; } while (Status != STATUS_NO_MORE_ENTRIES); ExFreePool(kvfi); } else if (dispos == REG_CREATED_NEW_KEY) { static const WCHAR builtin_users[] = L"S-1-5-32-545"; UNICODE_STRING us2; DWORD val; // If we're creating the key for the first time, we add a default mapping of // BUILTIN\Users to gid 100, which ought to correspond to the "users" group on Linux. us2.Length = us2.MaximumLength = sizeof(builtin_users) - sizeof(WCHAR); us2.Buffer = ExAllocatePoolWithTag(PagedPool, us2.MaximumLength, ALLOC_TAG); if (us2.Buffer) { RtlCopyMemory(us2.Buffer, builtin_users, us2.Length); val = 100; Status = ZwSetValueKey(h, &us2, 0, REG_DWORD, &val, sizeof(DWORD)); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); ZwClose(h); return; } add_group_mapping(us2.Buffer, us2.Length / sizeof(WCHAR), val); ExFreePool(us2.Buffer); } } ZwClose(h); } static void get_registry_value(HANDLE h, WCHAR* string, ULONG type, void* val, ULONG size) { ULONG kvfilen; KEY_VALUE_FULL_INFORMATION* kvfi; UNICODE_STRING us; NTSTATUS Status; RtlInitUnicodeString(&us, string); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if (kvfi->Type == type && kvfi->DataLength >= size) { RtlCopyMemory(val, ((uint8_t*)kvfi) + kvfi->DataOffset, size); } else { Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteValueKey returned %08lx\n", Status); } Status = ZwSetValueKey(h, &us, 0, type, val, size); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); } } } ExFreePool(kvfi); } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = ZwSetValueKey(h, &us, 0, type, val, size); if (!NT_SUCCESS(Status)) { ERR("ZwSetValueKey returned %08lx\n", Status); } } else { ERR("ZwQueryValueKey returned %08lx\n", Status); } } void read_registry(PUNICODE_STRING regpath, bool refresh) { OBJECT_ATTRIBUTES oa; NTSTATUS Status; HANDLE h; ULONG dispos; #ifdef _DEBUG KEY_VALUE_FULL_INFORMATION* kvfi; ULONG kvfilen, old_debug_log_level = debug_log_level; UNICODE_STRING us, old_log_file, old_log_device; static const WCHAR def_log_file[] = L"\\??\\C:\\btrfs.log"; #endif ExAcquireResourceExclusiveLite(&mapping_lock, true); read_mappings(regpath); read_group_mappings(regpath); ExReleaseResourceLite(&mapping_lock); InitializeObjectAttributes(&oa, regpath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateKey(&h, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos); if (!NT_SUCCESS(Status)) { ERR("ZwCreateKey returned %08lx\n", Status); return; } if (!refresh) reset_subkeys(h, regpath); get_registry_value(h, L"Compress", REG_DWORD, &mount_compress, sizeof(mount_compress)); get_registry_value(h, L"CompressForce", REG_DWORD, &mount_compress_force, sizeof(mount_compress_force)); get_registry_value(h, L"CompressType", REG_DWORD, &mount_compress_type, sizeof(mount_compress_type)); get_registry_value(h, L"ZlibLevel", REG_DWORD, &mount_zlib_level, sizeof(mount_zlib_level)); get_registry_value(h, L"FlushInterval", REG_DWORD, &mount_flush_interval, sizeof(mount_flush_interval)); get_registry_value(h, L"MaxInline", REG_DWORD, &mount_max_inline, sizeof(mount_max_inline)); get_registry_value(h, L"SkipBalance", REG_DWORD, &mount_skip_balance, sizeof(mount_skip_balance)); get_registry_value(h, L"NoBarrier", REG_DWORD, &mount_no_barrier, sizeof(mount_no_barrier)); get_registry_value(h, L"NoTrim", REG_DWORD, &mount_no_trim, sizeof(mount_no_trim)); get_registry_value(h, L"ClearCache", REG_DWORD, &mount_clear_cache, sizeof(mount_clear_cache)); get_registry_value(h, L"AllowDegraded", REG_DWORD, &mount_allow_degraded, sizeof(mount_allow_degraded)); get_registry_value(h, L"Readonly", REG_DWORD, &mount_readonly, sizeof(mount_readonly)); get_registry_value(h, L"ZstdLevel", REG_DWORD, &mount_zstd_level, sizeof(mount_zstd_level)); get_registry_value(h, L"NoRootDir", REG_DWORD, &mount_no_root_dir, sizeof(mount_no_root_dir)); if (!refresh) get_registry_value(h, L"NoPNP", REG_DWORD, &no_pnp, sizeof(no_pnp)); if (mount_flush_interval == 0) mount_flush_interval = 1; #ifdef _DEBUG get_registry_value(h, L"DebugLogLevel", REG_DWORD, &debug_log_level, sizeof(debug_log_level)); RtlInitUnicodeString(&us, L"LogDevice"); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); old_log_device = log_device; log_device.Length = log_device.MaximumLength = 0; log_device.Buffer = NULL; if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) { log_device.Length = log_device.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength); log_device.Buffer = ExAllocatePoolWithTag(PagedPool, log_device.MaximumLength, ALLOC_TAG); if (!log_device.Buffer) { ERR("out of memory\n"); ExFreePool(kvfi); ZwClose(h); return; } RtlCopyMemory(log_device.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_device.Length); if (log_device.Buffer[(log_device.Length / sizeof(WCHAR)) - 1] == 0) log_device.Length -= sizeof(WCHAR); } else { ERR("LogDevice was type %lu, length %lu\n", kvfi->Type, kvfi->DataLength); Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) { ERR("ZwDeleteValueKey returned %08lx\n", Status); } } } ExFreePool(kvfi); } else if (Status != STATUS_OBJECT_NAME_NOT_FOUND) { ERR("ZwQueryValueKey returned %08lx\n", Status); } ExAcquireResourceExclusiveLite(&log_lock, true); if (refresh && (log_device.Length != old_log_device.Length || RtlCompareMemory(log_device.Buffer, old_log_device.Buffer, log_device.Length) != log_device.Length || (!comfo && log_device.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) { if (comfo) ObDereferenceObject(comfo); if (log_handle) { ZwClose(log_handle); log_handle = NULL; } comfo = NULL; comdo = NULL; if (log_device.Length > 0 && debug_log_level > 0) { Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo); if (!NT_SUCCESS(Status)) DbgPrint("IoGetDeviceObjectPointer returned %08lx\n", Status); } } ExReleaseResourceLite(&log_lock); if (old_log_device.Buffer) ExFreePool(old_log_device.Buffer); RtlInitUnicodeString(&us, L"LogFile"); kvfi = NULL; kvfilen = 0; Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); old_log_file = log_file; if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) { kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); if (!kvfi) { ERR("out of memory\n"); ZwClose(h); return; } Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen); if (NT_SUCCESS(Status)) { if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) { log_file.Length = log_file.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength); log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG); if (!log_file.Buffer) { ERR("out of memory\n"); ExFreePool(kvfi); ZwClose(h); return; } RtlCopyMemory(log_file.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_file.Length); if (log_file.Buffer[(log_file.Length / sizeof(WCHAR)) - 1] == 0) log_file.Length -= sizeof(WCHAR); } else { ERR("LogFile was type %lu, length %lu\n", kvfi->Type, kvfi->DataLength); Status = ZwDeleteValueKey(h, &us); if (!NT_SUCCESS(Status)) ERR("ZwDeleteValueKey returned %08lx\n", Status); log_file.Length = 0; } } else { ERR("ZwQueryValueKey returned %08lx\n", Status); log_file.Length = 0; } ExFreePool(kvfi); } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { Status = ZwSetValueKey(h, &us, 0, REG_SZ, (void*)def_log_file, sizeof(def_log_file)); if (!NT_SUCCESS(Status)) ERR("ZwSetValueKey returned %08lx\n", Status); log_file.Length = 0; } else { ERR("ZwQueryValueKey returned %08lx\n", Status); log_file.Length = 0; } if (log_file.Length == 0) { log_file.Length = log_file.MaximumLength = sizeof(def_log_file) - sizeof(WCHAR); log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG); if (!log_file.Buffer) { ERR("out of memory\n"); ZwClose(h); return; } RtlCopyMemory(log_file.Buffer, def_log_file, log_file.Length); } ExAcquireResourceExclusiveLite(&log_lock, true); if (refresh && (log_file.Length != old_log_file.Length || RtlCompareMemory(log_file.Buffer, old_log_file.Buffer, log_file.Length) != log_file.Length || (!log_handle && log_file.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) { if (log_handle) { ZwClose(log_handle); log_handle = NULL; } if (!comfo && log_file.Length > 0 && refresh && debug_log_level > 0) { IO_STATUS_BLOCK iosb; InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0); if (!NT_SUCCESS(Status)) { DbgPrint("ZwCreateFile returned %08lx\n", Status); log_handle = NULL; } } } ExReleaseResourceLite(&log_lock); if (old_log_file.Buffer) ExFreePool(old_log_file.Buffer); #endif ZwClose(h); } _Function_class_(WORKER_THREAD_ROUTINE) static void __stdcall registry_work_item(PVOID Parameter) { NTSTATUS Status; HANDLE regh = (HANDLE)Parameter; IO_STATUS_BLOCK iosb; TRACE("registry changed\n"); read_registry(®istry_path, true); Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true); if (!NT_SUCCESS(Status)) ERR("ZwNotifyChangeKey returned %08lx\n", Status); } void watch_registry(HANDLE regh) { NTSTATUS Status; IO_STATUS_BLOCK iosb; ExInitializeWorkItem(&wqi, registry_work_item, regh); Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true); if (!NT_SUCCESS(Status)) ERR("ZwNotifyChangeKey returned %08lx\n", Status); }