reactos/modules/rostests/apitests/ntdll/NtLoadUnloadKey.c
Hermès Bélusca-Maïto 9393fc320e
[FORMATTING] Remove trailing whitespace. Addendum to 34593d93.
Excluded: 3rd-party code (incl. wine) and most of the win32ss.
2021-09-13 03:52:22 +02:00

629 lines
20 KiB
C

/*
* PROJECT: ReactOS API Tests
* LICENSE: GPLv2+ - See COPYING in the top level directory
* PURPOSE: Test for NtLoadKey and NtUnloadKey
* PROGRAMMERS: Hermes Belusca-Maito (hermes.belusca@sfr.fr)
*/
#include "precomp.h"
/* See xdk/cmtypes.h */
#define REG_CREATED_NEW_KEY 1
#define REG_OPENED_EXISTING_KEY 2
#define REG_FORCE_UNLOAD 1
#if 1
#define NDEBUG
#include <debug.h>
#else
#define DPRINT(fmt, ...) printf("(%s:%d) " fmt, __FILE__, __LINE__, ##__VA_ARGS__);
#define DPRINT1(fmt, ...) printf("(%s:%d) " fmt, __FILE__, __LINE__, ##__VA_ARGS__);
#endif
static NTSTATUS (NTAPI *pNtUnloadKey2)(POBJECT_ATTRIBUTES, ULONG);
static BOOLEAN
RetrieveCurrentModuleNTDirectory(
OUT PUNICODE_STRING NtPath)
{
WCHAR ModulePath[MAX_PATH];
PWSTR PathSep;
/* Retrieve the current path where the test is running */
GetModuleFileNameW(NULL, ModulePath, _countof(ModulePath));
PathSep = wcsrchr(ModulePath, L'\\');
if (!PathSep)
PathSep = ModulePath + wcslen(ModulePath);
*PathSep = UNICODE_NULL;
/* Convert the path to NT format and work with it for now on */
return RtlDosPathNameToNtPathName_U(ModulePath, NtPath, NULL, NULL);
}
static NTSTATUS
CreateRegKey(
OUT PHANDLE KeyHandle,
IN HANDLE RootKey OPTIONAL,
IN PUNICODE_STRING KeyName,
IN ULONG CreateOptions,
OUT PULONG Disposition OPTIONAL)
{
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
KeyName,
OBJ_CASE_INSENSITIVE,
RootKey,
NULL);
return NtCreateKey(KeyHandle,
KEY_ALL_ACCESS,
&ObjectAttributes,
0,
NULL,
CreateOptions,
Disposition);
}
static NTSTATUS
CreateProtoHive(
OUT PHANDLE KeyHandle)
{
NTSTATUS Status;
UNICODE_STRING KeyName;
RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\SYSTEM\\$$$PROTO.HIV");
Status = CreateRegKey(KeyHandle,
NULL,
&KeyName,
REG_OPTION_NON_VOLATILE,
NULL);
if (!NT_SUCCESS(Status))
return Status;
NtFlushKey(KeyHandle);
return Status;
}
static VOID
DestroyProtoHive(
IN HANDLE KeyHandle)
{
NtDeleteKey(KeyHandle);
NtClose(KeyHandle);
}
static NTSTATUS
OpenDirectoryByHandleOrPath(
OUT PHANDLE RootPathHandle,
IN HANDLE RootDirectory OPTIONAL,
IN PUNICODE_STRING RootPath OPTIONAL)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
*RootPathHandle = NULL;
/*
* RootDirectory and RootPath cannot be either both NULL
* or both non-NULL, when being specified.
*/
if ((!RootDirectory && !RootPath) ||
( RootDirectory && RootPath))
{
return STATUS_INVALID_PARAMETER;
}
if (!RootDirectory && RootPath)
{
/* Open the root directory path */
InitializeObjectAttributes(&ObjectAttributes,
RootPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtOpenFile(RootPathHandle,
// FILE_TRAVERSE is needed to be able to use the handle as RootDirectory for future InitializeObjectAttributes calls.
FILE_LIST_DIRECTORY | FILE_ADD_FILE /* | FILE_ADD_SUBDIRECTORY */ | FILE_TRAVERSE | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE /* | FILE_OPEN_FOR_BACKUP_INTENT */);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtOpenFile(%wZ) failed, Status 0x%08lx\n", RootPath, Status);
return Status;
}
/* Mark the handle as being opened locally */
*RootPathHandle = (HANDLE)((ULONG_PTR)*RootPathHandle | 1);
}
else if (RootDirectory && !RootPath)
{
*RootPathHandle = RootDirectory;
}
// No other cases possible
return STATUS_SUCCESS;
}
/*
* Should be called under privileges
*/
static NTSTATUS
CreateRegistryFile(
IN HANDLE RootDirectory OPTIONAL,
IN PUNICODE_STRING RootPath OPTIONAL,
IN PCWSTR RegistryKey,
IN HANDLE ProtoKeyHandle)
{
NTSTATUS Status;
HANDLE RootPathHandle, FileHandle;
UNICODE_STRING FileName;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
/* Open the root directory */
Status = OpenDirectoryByHandleOrPath(&RootPathHandle, RootDirectory, RootPath);
if (!NT_SUCCESS(Status))
{
DPRINT1("OpenDirectoryByHandleOrPath failed, Status 0x%08lx\n", Status);
return Status;
}
/* Create the file */
RtlInitUnicodeString(&FileName, RegistryKey);
InitializeObjectAttributes(&ObjectAttributes,
&FileName,
OBJ_CASE_INSENSITIVE,
(HANDLE)((ULONG_PTR)RootPathHandle & ~1), // Remove the opened-locally flag
NULL);
Status = NtCreateFile(&FileHandle,
FILE_GENERIC_WRITE /* | DELETE */,
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL /* | FILE_FLAG_DELETE_ON_CLOSE */,
0,
FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
NULL,
0);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtCreateFile(%wZ) failed, Status 0x%08lx\n", &FileName, Status);
goto Cleanup;
}
/* Save the selected hive into the file */
Status = NtSaveKeyEx(ProtoKeyHandle, FileHandle, REG_LATEST_FORMAT);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtSaveKeyEx(%wZ) failed, Status 0x%08lx\n", &FileName, Status);
}
/* Close the file, the root directory (if opened locally), and return */
NtClose(FileHandle);
Cleanup:
if ((ULONG_PTR)RootPathHandle & 1) NtClose((HANDLE)((ULONG_PTR)RootPathHandle & ~1));
return Status;
}
/*
* Should be called under privileges
*/
static NTSTATUS
MyDeleteFile(
IN HANDLE RootDirectory OPTIONAL,
IN PUNICODE_STRING RootPath OPTIONAL,
IN PCWSTR FileName,
IN BOOLEAN ForceDelete) // ForceDelete can be used to delete read-only files
{
NTSTATUS Status;
HANDLE RootPathHandle;
UNICODE_STRING NtPath;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle;
FILE_DISPOSITION_INFORMATION FileDispInfo;
BOOLEAN RetryOnce = FALSE;
/* Open the root directory */
Status = OpenDirectoryByHandleOrPath(&RootPathHandle, RootDirectory, RootPath);
if (!NT_SUCCESS(Status))
{
DPRINT1("OpenDirectoryByHandleOrPath failed, Status 0x%08lx\n", Status);
return Status;
}
/* Open the directory name that was passed in */
RtlInitUnicodeString(&NtPath, FileName);
InitializeObjectAttributes(&ObjectAttributes,
&NtPath,
OBJ_CASE_INSENSITIVE,
RootPathHandle,
NULL);
Retry: /* We go back there once if RetryOnce == TRUE */
Status = NtOpenFile(&FileHandle,
DELETE | FILE_READ_ATTRIBUTES |
(RetryOnce ? FILE_WRITE_ATTRIBUTES : 0),
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtOpenFile failed with Status 0x%08lx\n", Status);
return Status;
}
if (RetryOnce)
{
FILE_BASIC_INFORMATION FileInformation;
Status = NtQueryInformationFile(FileHandle,
&IoStatusBlock,
&FileInformation,
sizeof(FILE_BASIC_INFORMATION),
FileBasicInformation);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtQueryInformationFile failed with Status 0x%08lx\n", Status);
NtClose(FileHandle);
return Status;
}
FileInformation.FileAttributes = FILE_ATTRIBUTE_NORMAL;
Status = NtSetInformationFile(FileHandle,
&IoStatusBlock,
&FileInformation,
sizeof(FILE_BASIC_INFORMATION),
FileBasicInformation);
NtClose(FileHandle);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtSetInformationFile failed with Status 0x%08lx\n", Status);
return Status;
}
}
/* Ask for the file to be deleted */
FileDispInfo.DeleteFile = TRUE;
Status = NtSetInformationFile(FileHandle,
&IoStatusBlock,
&FileDispInfo,
sizeof(FILE_DISPOSITION_INFORMATION),
FileDispositionInformation);
NtClose(FileHandle);
if (!NT_SUCCESS(Status))
DPRINT1("Deletion of file '%S' failed, Status 0x%08lx\n", FileName, Status);
// FIXME: Check the precise value of Status!
if (!NT_SUCCESS(Status) && ForceDelete && !RetryOnce)
{
/* Retry once */
RetryOnce = TRUE;
goto Retry;
}
/* Return result to the caller */
return Status;
}
/*
* Should be called under privileges
*/
static NTSTATUS
ConnectRegistry(
IN HANDLE RootKey OPTIONAL,
IN PCWSTR RegMountPoint,
IN HANDLE RootDirectory OPTIONAL,
IN PUNICODE_STRING RootPath OPTIONAL,
IN PCWSTR RegistryKey)
{
NTSTATUS Status;
HANDLE RootPathHandle;
UNICODE_STRING KeyName, FileName;
OBJECT_ATTRIBUTES KeyObjectAttributes;
OBJECT_ATTRIBUTES FileObjectAttributes;
/* Open the root directory */
Status = OpenDirectoryByHandleOrPath(&RootPathHandle, RootDirectory, RootPath);
if (!NT_SUCCESS(Status))
{
DPRINT1("OpenDirectoryByHandleOrPath failed, Status 0x%08lx\n", Status);
return Status;
}
RtlInitUnicodeString(&KeyName, RegMountPoint);
InitializeObjectAttributes(&KeyObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE,
RootKey,
NULL);
RtlInitUnicodeString(&FileName, RegistryKey);
InitializeObjectAttributes(&FileObjectAttributes,
&FileName,
OBJ_CASE_INSENSITIVE,
(HANDLE)((ULONG_PTR)RootPathHandle & ~1), // Remove the opened-locally flag
NULL);
/* Mount the registry hive in the registry namespace */
Status = NtLoadKey(&KeyObjectAttributes, &FileObjectAttributes);
/* Close the root directory (if opened locally), and return */
if ((ULONG_PTR)RootPathHandle & 1) NtClose((HANDLE)((ULONG_PTR)RootPathHandle & ~1));
return Status;
}
/*
* Should be called under privileges
*/
static NTSTATUS
DisconnectRegistry(
IN HANDLE RootKey OPTIONAL,
IN PCWSTR RegMountPoint,
IN ULONG Flags)
{
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
RtlInitUnicodeString(&KeyName, RegMountPoint);
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE,
RootKey,
NULL);
if (!pNtUnloadKey2)
{
win_skip("NtUnloadKey2 unavailable, using NtUnloadKey. Flags %lu\n", Flags);
return NtUnloadKey(&ObjectAttributes);
}
return pNtUnloadKey2(&ObjectAttributes, Flags);
}
START_TEST(NtLoadUnloadKey)
{
typedef struct _HIVE_LIST_ENTRY
{
PCWSTR HiveName;
PCWSTR RegMountPoint;
} HIVE_LIST_ENTRY;
static const HIVE_LIST_ENTRY RegistryHives[] =
{
{ L"TestHive1", L"\\Registry\\Machine\\TestHive1" },
{ L"TestHive2", L"\\Registry\\Machine\\TestHive2" },
};
NTSTATUS Status;
UNICODE_STRING NtTestPath;
UNICODE_STRING KeyName;
HANDLE KeyHandle;
ULONG Disposition;
UINT i;
BOOLEAN PrivilegeSet[2] = {FALSE, FALSE};
WCHAR PathBuffer[MAX_PATH];
pNtUnloadKey2 = (PVOID)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtUnloadKey2");
/* Retrieve our current directory */
RetrieveCurrentModuleNTDirectory(&NtTestPath);
/* Acquire restore privilege */
Status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &PrivilegeSet[0]);
if (!NT_SUCCESS(Status))
{
skip("RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE) failed (Status 0x%08lx)\n", Status);
/* Exit prematurely here.... */
// goto Cleanup;
RtlFreeUnicodeString(&NtTestPath);
return;
}
/* Acquire backup privilege */
Status = RtlAdjustPrivilege(SE_BACKUP_PRIVILEGE, TRUE, FALSE, &PrivilegeSet[1]);
if (!NT_SUCCESS(Status))
{
skip("RtlAdjustPrivilege(SE_BACKUP_PRIVILEGE) failed (Status 0x%08lx)\n", Status);
RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, PrivilegeSet[0], FALSE, &PrivilegeSet[0]);
/* Exit prematurely here.... */
// goto Cleanup;
RtlFreeUnicodeString(&NtTestPath);
return;
}
/* Create the template proto-hive */
Status = CreateProtoHive(&KeyHandle);
if (!NT_SUCCESS(Status))
{
skip("CreateProtoHive() failed to create the proto-hive; Status 0x%08lx\n", Status);
goto Cleanup;
}
/* Create two registry hive files from it */
for (i = 0; i < _countof(RegistryHives); ++i)
{
Status = CreateRegistryFile(NULL, &NtTestPath,
RegistryHives[i].HiveName,
KeyHandle);
if (!NT_SUCCESS(Status))
{
DPRINT1("CreateRegistryFile(%S) failed, Status 0x%08lx\n", RegistryHives[i].HiveName, Status);
/* Exit prematurely here.... */
break;
}
}
/* That is now done, remove the proto-hive */
DestroyProtoHive(KeyHandle);
/* Exit prematurely here if we failed */
if (!NT_SUCCESS(Status))
goto Cleanup;
/***********************************************************************************************/
/* Now, mount the first hive */
Status = ConnectRegistry(NULL, RegistryHives[0].RegMountPoint,
NULL, &NtTestPath,
RegistryHives[0].HiveName);
if (!NT_SUCCESS(Status))
{
DPRINT1("ConnectRegistry('%wZ\\%S', '%S') failed, Status 0x%08lx\n",
&NtTestPath, RegistryHives[0].HiveName, RegistryHives[0].RegMountPoint, Status);
}
/* Create or open a key inside the mounted hive */
StringCchPrintfW(PathBuffer, _countof(PathBuffer), L"%s\\%s", RegistryHives[0].RegMountPoint, L"MyKey_1");
RtlInitUnicodeString(&KeyName, PathBuffer);
KeyHandle = NULL;
Status = CreateRegKey(&KeyHandle,
NULL,
&KeyName,
REG_OPTION_NON_VOLATILE,
&Disposition);
if (!NT_SUCCESS(Status))
{
DPRINT1("CreateRegKey(%wZ) failed (Status %lx)\n", &KeyName, Status);
}
else
{
DPRINT1("CreateRegKey(%wZ) succeeded to %s the key (Status %lx)\n",
&KeyName,
Disposition == REG_CREATED_NEW_KEY ? "create" : /* REG_OPENED_EXISTING_KEY */ "open",
Status);
}
/* The key handle must be valid here */
Status = NtFlushKey(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Attempt to unmount the hive, with the handle key still opened */
Status = DisconnectRegistry(NULL, RegistryHives[0].RegMountPoint, 0); // Same as NtUnloadKey(&ObjectAttributes);
DPRINT1("Unmounting '%S' %s\n", RegistryHives[0].RegMountPoint, NT_SUCCESS(Status) ? "succeeded" : "failed");
ok_ntstatus(Status, STATUS_CANNOT_DELETE);
/* The key handle should still be valid here */
Status = NtFlushKey(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Force-unmount the hive, with the handle key still opened */
Status = DisconnectRegistry(NULL, RegistryHives[0].RegMountPoint, REG_FORCE_UNLOAD);
DPRINT1("Force-unmounting '%S' %s\n", RegistryHives[0].RegMountPoint, NT_SUCCESS(Status) ? "succeeded" : "failed");
ok_hex(Status, STATUS_SUCCESS);
/* The key handle should not be valid anymore */
Status = NtFlushKey(KeyHandle);
if (Status != STATUS_KEY_DELETED /* Win2k3 */ &&
Status != STATUS_HIVE_UNLOADED /* Win7+ */)
{
ok_ntstatus(Status, STATUS_KEY_DELETED);
}
/* The key handle should not be valid anymore */
Status = NtDeleteKey(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Close by principle the handle, but should this fail? */
Status = NtClose(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/***********************************************************************************************/
/* Now, mount the first hive, again */
Status = ConnectRegistry(NULL, RegistryHives[0].RegMountPoint,
NULL, &NtTestPath,
RegistryHives[0].HiveName);
if (!NT_SUCCESS(Status))
{
DPRINT1("ConnectRegistry('%wZ\\%S', '%S') failed, Status 0x%08lx\n",
&NtTestPath, RegistryHives[0].HiveName, RegistryHives[0].RegMountPoint, Status);
}
/* Create or open a key inside the mounted hive */
StringCchPrintfW(PathBuffer, _countof(PathBuffer), L"%s\\%s", RegistryHives[0].RegMountPoint, L"MyKey_2");
RtlInitUnicodeString(&KeyName, PathBuffer);
KeyHandle = NULL;
Status = CreateRegKey(&KeyHandle,
NULL,
&KeyName,
REG_OPTION_NON_VOLATILE,
&Disposition);
if (!NT_SUCCESS(Status))
{
DPRINT1("CreateRegKey(%wZ) failed (Status %lx)\n", &KeyName, Status);
}
else
{
DPRINT1("CreateRegKey(%wZ) succeeded to %s the key (Status %lx)\n",
&KeyName,
Disposition == REG_CREATED_NEW_KEY ? "create" : /* REG_OPENED_EXISTING_KEY */ "open",
Status);
}
/* The key handle must be valid here */
Status = NtFlushKey(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Delete the key, this should succeed */
Status = NtDeleteKey(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Close the handle, this should succeed */
Status = NtClose(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
/* Attempt to unmount the hive (no forcing), this should succeed */
Status = DisconnectRegistry(NULL, RegistryHives[0].RegMountPoint, 0); // Same as NtUnloadKey(&ObjectAttributes);
DPRINT1("Unmounting '%S' %s\n", RegistryHives[0].RegMountPoint, NT_SUCCESS(Status) ? "succeeded" : "failed");
ok_ntstatus(Status, STATUS_SUCCESS);
/* Force-unmount the hive (it is already unmounted), this should fail */
Status = DisconnectRegistry(NULL, RegistryHives[0].RegMountPoint, REG_FORCE_UNLOAD);
DPRINT1("Force-unmounting '%S' %s\n", RegistryHives[0].RegMountPoint, NT_SUCCESS(Status) ? "succeeded" : "failed");
ok_hex(Status, STATUS_INVALID_PARAMETER);
#if 0
/* Close by principle the handle, but should this fail? */
Status = NtClose(KeyHandle);
ok_ntstatus(Status, STATUS_SUCCESS);
#endif
/***********************************************************************************************/
Cleanup:
/* Destroy the hive files */
for (i = 0; i < _countof(RegistryHives); ++i)
{
Status = MyDeleteFile(NULL, &NtTestPath,
RegistryHives[i].HiveName, TRUE);
if (!NT_SUCCESS(Status))
DPRINT1("MyDeleteFile(%S) failed, Status 0x%08lx\n", RegistryHives[i].HiveName, Status);
}
/* Remove restore and backup privileges */
RtlAdjustPrivilege(SE_BACKUP_PRIVILEGE, PrivilegeSet[1], FALSE, &PrivilegeSet[1]);
RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, PrivilegeSet[0], FALSE, &PrivilegeSet[0]);
RtlFreeUnicodeString(&NtTestPath);
}