// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "sandbox/win/src/win_utils.h"
#include <windows.h>
#include <ntstatus.h>
#include <psapi.h>
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/numerics/safe_math.h"
#include "base/strings/string_util.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "sandbox/win/src/internal_types.h"
#include "sandbox/win/src/nt_internals.h"
#include "sandbox/win/src/sandbox_nt_util.h"
namespace {
NTSTATUS WrapQueryObject(HANDLE handle,
OBJECT_INFORMATION_CLASS info_class,
std::vector<uint8_t>& buffer,
PULONG reqd) {
if (handle == nullptr || handle == INVALID_HANDLE_VALUE)
return STATUS_INVALID_PARAMETER;
NtQueryObjectFunction NtQueryObject = sandbox::GetNtExports()->QueryObject;
ULONG size = static_cast<ULONG>(buffer.size());
__try {
return NtQueryObject(handle, info_class, buffer.data(), size, reqd);
} __except (GetExceptionCode() == STATUS_INVALID_HANDLE
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH) {
return STATUS_INVALID_PARAMETER;
}
}
// `hint` is used for the initial call to NtQueryObject. Note that some data
// in the returned vector might be unused.
std::unique_ptr<std::vector<uint8_t>> QueryObjectInformation(
HANDLE handle,
OBJECT_INFORMATION_CLASS info_class,
ULONG hint) {
// Internal pointers in this buffer cannot move about so cannot just return
// the vector.
auto data = std::make_unique<std::vector<uint8_t>>(hint);
ULONG req = 0;
NTSTATUS ret = WrapQueryObject(handle, info_class, *data, &req);
if (ret == STATUS_INFO_LENGTH_MISMATCH || ret == STATUS_BUFFER_TOO_SMALL ||
ret == STATUS_BUFFER_OVERFLOW) {
data->resize(req);
ret = WrapQueryObject(handle, info_class, *data, nullptr);
}
if (!NT_SUCCESS(ret))
return nullptr;
return data;
}
} // namespace
namespace sandbox {
bool IsPipe(const std::wstring& path) {
std::wstring prefix = sandbox::kNTPrefix;
prefix += L"pipe\\";
return base::StartsWith(path, prefix, base::CompareCase::INSENSITIVE_ASCII);
}
std::optional<std::wstring> GetNtPathFromWin32Path(const std::wstring& path) {
base::win::ScopedHandle file(::CreateFileW(
path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
if (!file.is_valid()) {
return std::nullopt;
}
return GetPathFromHandle(file.get());
}
std::optional<std::wstring> GetPathFromHandle(HANDLE handle) {
auto buffer = QueryObjectInformation(handle, ObjectNameInformation, 512);
if (!buffer)
return std::nullopt;
OBJECT_NAME_INFORMATION* name =
reinterpret_cast<OBJECT_NAME_INFORMATION*>(buffer->data());
return std::wstring(
name->ObjectName.Buffer,
name->ObjectName.Length / sizeof(name->ObjectName.Buffer[0]));
}
std::optional<std::wstring> GetTypeNameFromHandle(HANDLE handle) {
// No typename is currently longer than 32 characters on Windows 11, so use a
// hint of 128 bytes.
auto buffer = QueryObjectInformation(handle, ObjectTypeInformation, 128);
if (!buffer)
return std::nullopt;
OBJECT_TYPE_INFORMATION* name =
reinterpret_cast<OBJECT_TYPE_INFORMATION*>(buffer->data());
return std::wstring(name->Name.Buffer,
name->Name.Length / sizeof(name->Name.Buffer[0]));
}
bool CopyToChildMemory(HANDLE child,
const base::span<uint8_t> local_buffer,
void** remote_buffer) {
DCHECK(remote_buffer);
if (local_buffer.empty()) {
*remote_buffer = nullptr;
return true;
}
// Allocate memory in the target process without specifying the address
void* remote_data = ::VirtualAllocEx(child, nullptr, local_buffer.size(),
MEM_COMMIT, PAGE_READWRITE);
if (!remote_data)
return false;
SIZE_T bytes_written;
bool success = ::WriteProcessMemory(child, remote_data, local_buffer.data(),
local_buffer.size(), &bytes_written);
if (!success || bytes_written != local_buffer.size()) {
::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE);
return false;
}
*remote_buffer = remote_data;
return true;
}
DWORD GetLastErrorFromNtStatus(NTSTATUS status) {
return GetNtExports()->RtlNtStatusToDosError(status);
}
// This function uses the undocumented PEB ImageBaseAddress field to extract
// the base address of the new process.
void* GetProcessBaseAddress(HANDLE process) {
PROCESS_BASIC_INFORMATION process_basic_info = {};
NTSTATUS status = GetNtExports()->QueryInformationProcess(
process, ProcessBasicInformation, &process_basic_info,
sizeof(process_basic_info), nullptr);
if (STATUS_SUCCESS != status)
return nullptr;
NT_PEB peb = {};
SIZE_T bytes_read = 0;
if (!::ReadProcessMemory(process, process_basic_info.PebBaseAddress, &peb,
sizeof(peb), &bytes_read) ||
(sizeof(peb) != bytes_read)) {
return nullptr;
}
void* base_address = peb.ImageBaseAddress;
char magic[2] = {};
if (!::ReadProcessMemory(process, base_address, magic, sizeof(magic),
&bytes_read) ||
(sizeof(magic) != bytes_read)) {
return nullptr;
}
if (magic[0] != 'M' || magic[1] != 'Z')
return nullptr;
return base_address;
}
bool ContainsNulCharacter(std::wstring_view str) {
wchar_t nul = '\0';
return str.find_first_of(nul) != std::wstring::npos;
}
} // namespace sandbox