Files
raven/native/nt/memory_api.cpp
T

346 lines
11 KiB
C++

#include "host_names.h"
#include "kernel_context.h"
#include "section.h"
#include "security_attributes.h"
#include "text.h"
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <map>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
namespace raven {
namespace {
struct View {
std::shared_ptr<SectionObject> mapping;
uint64_t offset = 0, size = 0;
bool writable = false;
};
// Where the named sections of the prefix live: one host file each, held by every process that has the section open.
std::filesystem::path section_directory()
{
std::filesystem::path base;
if (const char *runtime = std::getenv("XDG_RUNTIME_DIR"); runtime && *runtime)
base = runtime;
else
base = NativePrefix::from_environment().root();
auto directory = base / ("raven-sections-" + prefix_tag());
std::error_code error;
std::filesystem::create_directories(directory, error);
return directory;
}
std::string upper_utf8(const std::u16string &name)
{
auto text = to_utf8(name);
for (auto &character : text)
character = char(std::toupper(static_cast<unsigned char>(character)));
return text;
}
// Opens the host file of a named section and holds a shared lock on it. A creator that finds the file free makes it new; one that finds
// it held opens the section that exists. The lock is what tells a live section from the file a past run left.
int open_named_section(const std::u16string &name, uint64_t size, bool create, bool &existed, std::string &path, uint32_t &error)
{
existed = false;
path = (section_directory() / hex_text(name_hash(upper_utf8(name)))).string();
const int descriptor = ::open(path.c_str(), (create ? O_CREAT : 0) | O_RDWR | O_CLOEXEC, 0600);
if (descriptor < 0) {
error = errno == ENOENT ? 2 : 5;
return -1;
}
if (::flock(descriptor, LOCK_EX | LOCK_NB) == 0) {
if (create) {
if (::ftruncate(descriptor, 0) || ::ftruncate(descriptor, off_t(size))) {
::close(descriptor);
error = 8;
return -1;
}
::flock(descriptor, LOCK_SH);
return descriptor;
}
// Nobody holds the file: the section is gone.
::close(descriptor);
error = 2;
return -1;
}
::flock(descriptor, LOCK_SH);
existed = true;
return descriptor;
}
}
void register_memory_api(ApiRegistry &registry, KernelContext &context)
{
auto &allocator = context.allocator;
auto &memory = context.memory;
auto &objects = context.objects;
constexpr uint32_t zero_flag = 0x40;
registry.add("LocalAlloc", 2, [&](ApiCall &call) {
const auto address = allocator.allocate(call[1], call.u32(0) & zero_flag);
if (!address) {
call.fail(8);
return;
}
call.ret(address);
});
registry.add("GlobalAlloc", 2, [&](ApiCall &call) {
const auto address = allocator.allocate(call[1], call.u32(0) & zero_flag);
if (!address) {
call.fail(8);
return;
}
call.ret(address);
});
const auto free_block = [&](ApiCall &call) {
if (call[0] && !allocator.release(call[0])) {
call.fail(6, call[0]);
return;
}
call.ret(0);
};
registry.add("LocalFree", 1, free_block);
registry.add("GlobalFree", 1, free_block);
const auto lock_block = [&](ApiCall &call) {
if (allocator.size(call[0]) == UINT64_MAX) {
call.fail(6);
return;
}
call.ret(call[0]);
};
registry.add("LocalLock", 1, lock_block);
registry.add("GlobalLock", 1, lock_block);
const auto unlock_block = [](ApiCall &call) { call.set_last_error(0); };
registry.add("LocalUnlock", 1, unlock_block);
registry.add("GlobalUnlock", 1, unlock_block);
const auto size_block = [&](ApiCall &call) {
const auto size = allocator.size(call[0]);
if (size == UINT64_MAX) {
call.fail(6);
return;
}
call.ret(size);
};
registry.add("LocalSize", 1, size_block);
registry.add("GlobalSize", 1, size_block);
const auto resize_block = [&](ApiCall &call) {
const auto old = allocator.size(call[0]);
if (old == UINT64_MAX) {
call.fail(6);
return;
}
const auto address = allocator.allocate(call[1], call.u32(2) & zero_flag);
if (!address) {
call.fail(8);
return;
}
call.write(address, call.read(call[0], std::min<uint64_t>(old, call[1])));
allocator.release(call[0]);
call.ret(address);
};
registry.add("LocalReAlloc", 3, resize_block);
registry.add("GlobalReAlloc", 3, resize_block);
registry.add("GlobalMemoryStatusEx", 1, [](ApiCall &call) {
std::vector<uint8_t> bytes(64);
const uint32_t length = 64, load = 20;
std::memcpy(bytes.data(), &length, 4);
std::memcpy(bytes.data() + 4, &load, 4);
const uint64_t values[] = {16ULL << 30, 8ULL << 30, 32ULL << 30, 16ULL << 30, 0x7fffffe0000ULL, 0x7fffff00000ULL, 0};
std::memcpy(bytes.data() + 8, values, 56);
call.write(call[0], bytes);
call.ret(1);
});
registry.add("HeapSetInformation", 4, [](ApiCall &call) { call.ret(1); });
registry.add("HeapValidate", 3, [](ApiCall &call) { call.ret(1); });
registry.add("HeapCompact", 2, [](ApiCall &call) { call.ret(0x10000); });
registry.add("CreateFileMappingW", 6, [&](ApiCall &call) {
auto mapping = std::make_shared<SectionObject>();
const auto name = call[5] ? call.wide(call[5], 260) : std::u16string();
bool existed = false;
if (call[0] != UINT64_MAX) {
mapping->file = objects.object_as<FileObject>(call[0]);
if (!mapping->file) {
call.fail(6);
return;
}
struct stat metadata{};
::fstat(mapping->file->descriptor, &metadata);
mapping->size = (uint64_t(call.u32(4)) << 32 | call.u32(3)) ? (uint64_t(call.u32(4)) << 32 | call.u32(3)) : uint64_t(metadata.st_size);
} else {
mapping->size = uint64_t(call.u32(4)) << 32 | call.u32(3);
if (!mapping->size) {
call.fail(87);
return;
}
if (name.empty()) {
mapping->descriptor = ::memfd_create("raven-section", MFD_CLOEXEC);
if (mapping->descriptor < 0 || ::ftruncate(mapping->descriptor, off_t(mapping->size))) {
call.fail(8);
return;
}
} else {
uint32_t error = 0;
mapping->descriptor = open_named_section(name, mapping->size, true, existed, mapping->backing, error);
if (mapping->descriptor < 0) {
call.fail(error);
return;
}
mapping->name = name;
if (existed) {
struct stat metadata{};
::fstat(mapping->descriptor, &metadata);
mapping->size = uint64_t(metadata.st_size);
}
}
}
mapping->protection = call.u32(2);
const auto made = objects.add_object(mapping);
if (made.status) {
call.fail(8);
return;
}
// SECTION_ALL_ACCESS: the rights of a section are kept per handle so a copy can give some up.
objects.set_access(made.value, 0xf001f);
if (inheritable_handle(call, call[1]))
objects.set_inherit(made.value, true);
call.set_last_error(existed ? 183 : 0);
call.ret(made.value);
});
registry.add("OpenFileMappingW", 3, [&](ApiCall &call) {
if (!call[2]) {
call.fail(87);
return;
}
auto mapping = std::make_shared<SectionObject>();
mapping->name = call.wide(call[2], 260);
bool existed = false;
uint32_t error = 0;
mapping->descriptor = open_named_section(mapping->name, 0, false, existed, mapping->backing, error);
if (mapping->descriptor < 0) {
call.fail(error);
return;
}
struct stat metadata{};
::fstat(mapping->descriptor, &metadata);
mapping->size = uint64_t(metadata.st_size);
mapping->protection = 4;
const auto made = objects.add_object(mapping);
if (made.status) {
call.fail(8);
return;
}
objects.set_inherit(made.value, call.u32(1) != 0);
// An opened section carries the rights the caller asked for.
objects.set_access(made.value, call.u32(0) & ~0xf0000000U ? (call.u32(0) & ~0xf0000000U) : 0xf001f);
call.ret(made.value);
});
// SectionBasicInformation: the base address, the allocation attributes and the maximum size of the section.
registry.add("NtQuerySection", 5, [&](ApiCall &call) {
constexpr uint32_t status_invalid_handle = 0xc0000008U, status_invalid_info_class = 0xc0000003U, status_info_length_mismatch = 0xc0000004U, sec_commit = 0x8000000;
const auto mapping = objects.object_as<SectionObject>(call[0]);
if (!mapping) {
call.ret(status_invalid_handle);
return;
}
if (call.u32(1) != 0) {
call.ret(status_invalid_info_class);
return;
}
const size_t width = call.pointer_width();
const size_t layout = width == 8 ? 24 : 16;
if (call.u32(3) < layout) {
call.ret(status_info_length_mismatch);
return;
}
std::vector<uint8_t> bytes(layout, 0);
const uint32_t attributes = sec_commit;
std::memcpy(bytes.data() + width, &attributes, 4);
std::memcpy(bytes.data() + layout - 8, &mapping->size, 8);
call.write(call[2], bytes);
if (call[4])
call.store<uint32_t>(call[4], uint32_t(layout));
call.ret(0);
});
const auto views_owner = std::make_shared<std::map<uint64_t, View>>();
auto &views = *views_owner;
registry.add("MapViewOfFile", 5, [&, views_owner](ApiCall &call) {
const auto mapping = objects.object_as<SectionObject>(call[0]);
if (!mapping) {
call.fail(6);
return;
}
// The handle must carry the right for the kind of view: writing needs the map-write right, reading the map-read right.
if (mapping->descriptor >= 0) {
const uint32_t need = (call.u32(1) & 0x2) ? 0x2U : 0x4U;
if (!(objects.access_of(call[0]) & need)) {
call.fail(5);
return;
}
}
const uint64_t offset = uint64_t(call.u32(2)) << 32 | call.u32(3);
const uint64_t size = call[4] ? call[4] : mapping->size - offset;
if (offset > mapping->size || size > mapping->size - offset) {
call.fail(87);
return;
}
// A view of shared memory maps the host file itself; the other kind is an allocation the file is read into.
if (mapping->descriptor >= 0) {
const uint32_t rights = call.u32(1);
const uint32_t access = rights & 0x2 ? ((rights & 0x20) ? 0x40 : 0x04) : rights & 0x1 ? 0x08 : (rights & 0x20) ? 0x20 : 0x02;
const auto view = memory.map_view(mapping->descriptor, offset, size, access);
if (view.status) {
call.fail(view.status == 0xc0000017U ? 8 : 87);
return;
}
views[view.address] = {mapping, offset, size, bool(rights & 2)};
call.ret(view.address);
return;
}
const auto range = memory.allocate(0, (size + 4095) & ~uint64_t(4095), 0x3000, 4);
if (range.status) {
call.fail(8);
return;
}
if (mapping->file) {
std::vector<uint8_t> bytes(size);
const auto count = ::pread(mapping->file->descriptor, bytes.data(), size, off_t(offset));
if (count > 0)
call.write(range.address, std::span<const uint8_t>(bytes.data(), size_t(count)));
}
views[range.address] = {mapping, offset, size, bool(call.u32(1) & 2)};
call.ret(range.address);
});
const auto write_back = [&](uint64_t address, ApiCall &call) {
const auto found = views.find(address);
if (found == views.end() || !found->second.writable || !found->second.mapping->file)
return;
const auto bytes = call.read(address, found->second.size);
::pwrite(found->second.mapping->file->descriptor, bytes.data(), bytes.size(), off_t(found->second.offset));
};
registry.add("FlushViewOfFile", 2, [write_back](ApiCall &call) {
write_back(call[0], call);
call.ret(1);
});
registry.add("UnmapViewOfFile", 1, [&, write_back](ApiCall &call) {
if (!views.contains(call[0])) {
call.fail(998);
return;
}
write_back(call[0], call);
views.erase(call[0]);
memory.free(call[0], 0, 0x8000);
call.ret(1);
});
}
}