diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 7dd431e..eaa7b4c 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -65,7 +65,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") if(NOT NONCLIENT_SOURCE_HASH STREQUAL "dab51515f54adc39af43f088a02afe45a719476408bee3c7bef6113c25b180fc") message(FATAL_ERROR "Pinned ReactOS nonclient functions differ") endif() - add_library(raven_cpu STATIC cpu/native_cpu.cpp nt/environment.cpp nt/system.cpp nt/providers.cpp nt/api.cpp nt/unwind.cpp nt/seh.cpp nt/apiset.cpp nt/system_exports.cpp nt/text.cpp nt/paths.cpp nt/file_api.cpp nt/ansi_api.cpp nt/shlwapi_api.cpp nt/registry_api.cpp nt/shell_api.cpp nt/platform_api.cpp nt/hash.cpp nt/version_api.cpp nt/system_api.cpp nt/process_api.cpp nt/security_api.cpp nt/child_api.cpp nt/child_process.cpp nt/winsock_api.cpp nt/host_api.cpp nt/pipe.cpp nt/job_api.cpp nt/wave_api.cpp nt/inherit.cpp nt/handle_channel.cpp nt/elevation.cpp nt/service_api.cpp nt/crypt32_api.cpp nt/process_info_api.cpp nt/font_service.cpp nt/sync_api.cpp nt/threads.cpp nt/thread_api.cpp nt/text_api.cpp nt/memory_api.cpp nt/guest_allocator.cpp nt/module_api.cpp nt/loader.cpp nt/tls.cpp nt/exceptions.cpp nt/resources.cpp nt/memory.cpp nt/objects.cpp nt/object_api.cpp nt/heap.cpp desktop/objects.cpp desktop/nonclient.cpp desktop/api.cpp desktop/surface.cpp desktop/windows.cpp desktop/system.cpp desktop/user_api.cpp desktop/window_manager.cpp desktop/window_ansi.cpp desktop/menu_manager.cpp desktop/shared_pixels.cpp desktop/foreign_surface.cpp desktop/user_ext.cpp desktop/scrollbar.cpp desktop/window_long.cpp desktop/child_frame.cpp desktop/gdi.cpp desktop/gdi_api.cpp desktop/gdi_text.cpp desktop/theme.cpp) + add_library(raven_cpu STATIC cpu/native_cpu.cpp nt/environment.cpp nt/system.cpp nt/providers.cpp nt/api.cpp nt/unwind.cpp nt/seh.cpp nt/apiset.cpp nt/system_exports.cpp nt/text.cpp nt/paths.cpp nt/file_api.cpp nt/ansi_api.cpp nt/shlwapi_api.cpp nt/registry_api.cpp nt/shell_api.cpp nt/platform_api.cpp nt/hash.cpp nt/version_api.cpp nt/system_api.cpp nt/process_api.cpp nt/actctx.cpp nt/security_api.cpp nt/child_api.cpp nt/child_process.cpp nt/winsock_api.cpp nt/host_api.cpp nt/pipe.cpp nt/job_api.cpp nt/wave_api.cpp nt/inherit.cpp nt/handle_channel.cpp nt/elevation.cpp nt/service_api.cpp nt/crypt32_api.cpp nt/process_info_api.cpp nt/font_service.cpp nt/sync_api.cpp nt/threads.cpp nt/thread_api.cpp nt/text_api.cpp nt/memory_api.cpp nt/guest_allocator.cpp nt/module_api.cpp nt/loader.cpp nt/tls.cpp nt/exceptions.cpp nt/resources.cpp nt/memory.cpp nt/objects.cpp nt/object_api.cpp nt/heap.cpp desktop/objects.cpp desktop/nonclient.cpp desktop/api.cpp desktop/surface.cpp desktop/windows.cpp desktop/system.cpp desktop/user_api.cpp desktop/window_manager.cpp desktop/window_ansi.cpp desktop/menu_manager.cpp desktop/shared_pixels.cpp desktop/foreign_surface.cpp desktop/user_ext.cpp desktop/scrollbar.cpp desktop/window_long.cpp desktop/child_frame.cpp desktop/gdi.cpp desktop/gdi_api.cpp desktop/gdi_text.cpp desktop/theme.cpp) target_include_directories(raven_cpu PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set(NLS_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/upstream/reactos-nls) file(SHA256 ${NLS_SOURCE}/txt2nls.cpp NLS_TOOL_HASH) @@ -1222,6 +1222,23 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") ${fixture}/module-path/module-path.exe) set_tests_properties(native_module_path_${installer_arch} PROPERTIES TIMEOUT 120 PASS_REGULAR_EXPRESSION "Native entry returned 0 " ENVIRONMENT RAVEN_PREFIX=${fixture}/module-path-prefix) + # Side-by-side classes and visual styles: a program with the common controls 6 manifest gets the version 6 controls, contexts + # activate from a manifest file, and the styles draw the accent default button. + file(MAKE_DIRECTORY ${fixture}/actctx) + add_custom_command(OUTPUT ${fixture}/actctx/actctx.exe ${fixture}/actctx/actctx.manifest + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx.manifest ${fixture}/actctx/actctx.manifest + COMMAND ${resource_compiler} -O coff -i ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx.rc -o ${fixture}/actctx/actctx_res.o + COMMAND ${installer_compiler} -O2 -nostdlib -fno-stack-protector -fno-builtin -Wl,--entry,${installer_entry},--no-insert-timestamp + -o ${fixture}/actctx/actctx.exe ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx_client.c ${fixture}/actctx/actctx_res.o + -lcomctl32 -luxtheme -lgdi32 -luser32 -lkernel32 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/testdata/native_loader + DEPENDS ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx_client.c ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx.manifest + ${PROJECT_SOURCE_DIR}/testdata/native_loader/actctx.rc VERBATIM) + add_custom_target(actctx_fixture_${installer_arch} ALL DEPENDS ${fixture}/actctx/actctx.exe) + add_test(NAME native_activation_contexts_${installer_arch} COMMAND raven --run-process --execution=native --timeout-ms=60000 + ${fixture}/actctx/actctx.exe) + set_tests_properties(native_activation_contexts_${installer_arch} PROPERTIES TIMEOUT 120 PASS_REGULAR_EXPRESSION "Native entry returned 0 " + ENVIRONMENT RAVEN_PREFIX=${fixture}/actctx-prefix) # msiexec found by name in the system directory, installing and removing the package from the command line. add_test(NAME native_msiexec_${installer_arch} COMMAND raven --run-process --execution=native --timeout-ms=60000 ${fixture}/msiexec-client.exe) set_tests_properties(native_msiexec_${installer_arch} PROPERTIES TIMEOUT 300 PASS_REGULAR_EXPRESSION "Native entry returned 0 " diff --git a/native/desktop/class_info.cpp b/native/desktop/class_info.cpp index 110c15c..ba83040 100644 --- a/native/desktop/class_info.cpp +++ b/native/desktop/class_info.cpp @@ -13,7 +13,9 @@ uint64_t NativeWindows::class_info(std::string_view name,std::spanclass_id)->name; + std::string_view value=objects.window_class(window->class_id)->name; + // A class registered inside an activation context keeps its plain name for the program: the version before '!' stays internal. + if (const auto mark=value.find('!'); mark!=value.npos && value.find_first_not_of("0123456789.")==mark) value.remove_prefix(mark+1); const auto length=std::min(size_t(count-1),value.size()),width=name=="GetClassNameW" ? size_t(2) : size_t(1); std::vector bytes((length+1)*width); for (size_t i=0;i #include "windows.h" +#include "nt/actctx.h" #include "nt/text.h" #include "brush.h" #include "nt/nls.h" @@ -944,6 +945,11 @@ uint16_t NativeWindows::atom(uint64_t name, uint64_t instance) return objects.find_class(instance,uint16_t(name)); } auto value=text(name); + // A class an assembly of the thread's activation context declares is first looked for under its versioned name. + auto &contexts=activation_contexts(); + if (contexts.current_thread) + if (const auto versioned=contexts.lookup_name(contexts.current_thread(),value)) + if (const auto found=objects.find_class(instance,*versioned)) return found; for (auto &character : value) if (character>='A' && character<='Z') character=char(character+('a'-'A')); return objects.find_class(instance,value); @@ -2062,6 +2068,9 @@ bool NativeWindows::dispatch(std::string_view name) const auto procedure=read(bytes,P+shift,P), instance=read(bytes,2*P+8+shift,P); auto class_name=text(read(bytes,7*P+8+shift,P)); if (!procedure || class_name.empty()) throw uint32_t(87); + // Inside an activation context whose assembly declares the class, it registers under its versioned name. + if (auto &contexts=activation_contexts(); contexts.current_thread) + if (auto versioned=contexts.registered_name(contexts.current_thread(),class_name)) class_name=std::move(*versioned); if (objects.class_registered(instance,class_name,(style & 0x4000)!=0)) throw uint32_t(1410); DesktopClass definition{std::move(class_name),instance,procedure,background,uint32_t(window_extra),std::vector(size_t(class_extra)),style}; definition.icon=icon; definition.cursor=cursor; diff --git a/native/nt/actctx.cpp b/native/nt/actctx.cpp new file mode 100644 index 0000000..f054554 --- /dev/null +++ b/native/nt/actctx.cpp @@ -0,0 +1,449 @@ +#include "actctx.h" +#include "text.h" +#include "pe/resources.h" + +#include +#include +#include +#include + +namespace raven { + +namespace { + +std::string lowered(std::string_view text) +{ + std::string result(text); + for (auto &character : result) + if (character >= 'A' && character <= 'Z') + character = char(character + ('a' - 'A')); + return result; +} + +// The manifest text as bytes of ASCII and UTF-8: UTF-16 manifests, with or without a byte order mark, are narrowed. +std::string manifest_text(std::string_view bytes) +{ + if (bytes.size() >= 3 && uint8_t(bytes[0]) == 0xef && uint8_t(bytes[1]) == 0xbb && uint8_t(bytes[2]) == 0xbf) + return std::string(bytes.substr(3)); + const bool little = bytes.size() >= 2 && ((uint8_t(bytes[0]) == 0xff && uint8_t(bytes[1]) == 0xfe) || (bytes[0] == '<' && bytes[1] == 0)); + if (!little) + return std::string(bytes); + std::string narrow; + for (size_t at = uint8_t(bytes[0]) == 0xff ? 2 : 0; at + 1 < bytes.size(); at += 2) + narrow.push_back(bytes[at + 1] ? '?' : bytes[at]); + return narrow; +} + +// Versions compare component by component as numbers. +std::vector version_parts(std::string_view version) +{ + std::vector parts; + uint32_t value = 0; + for (const char character : version) { + if (character == '.') { + parts.push_back(value); + value = 0; + } else if (character >= '0' && character <= '9') { + value = value * 10 + uint32_t(character - '0'); + } + } + parts.push_back(value); + return parts; +} + +// The window classes comctl32 6 declares, which every prefix keeps in its side-by-side store as Windows does. +constexpr const char *common_control_classes[] = {"Button", "ButtonListBox", "ComboBoxEx32", "ComboLBox", "Combobox", "Edit", "Listbox", + "NativeFontCtl", "ReBarWindow32", "ScrollBar", "Static", "SysAnimate32", "SysDateTimePick32", "SysHeader32", "SysIPAddress32", "SysLink", + "SysListView32", "SysMonthCal32", "SysPager", "SysTabControl32", "SysTreeView32", "ToolbarWindow32", "msctls_hotkey32", "msctls_progress32", + "msctls_statusbar32", "msctls_trackbar32", "msctls_updown32", "tooltips_class32"}; + +std::string common_controls_manifest(std::string_view architecture, std::string_view version) +{ + std::string text = "\n" + "\n" + " \n \n"; + for (const auto *name : common_control_classes) + text += " " + std::string(name) + "\n"; + return text + " \n\n"; +} + +} + +ActivationContexts &activation_contexts() +{ + static ActivationContexts contexts; + return contexts; +} + +void ActivationContexts::attach(KernelContext &context) +{ + kernel = &context; + wide_process = context.entry.image.is_64; + current_thread = [&context] { return context.threads.current_id(); }; + // The common controls' manifests: comctl32 creates its contexts from them and registers its version 6 classes inside. + if (const auto store = context.paths.resolve(u"C:\\Windows\\WinSxS\\Manifests")) { + std::error_code error; + std::filesystem::create_directories(store->host, error); + for (const auto *architecture : {"amd64", "x86"}) + for (const auto *version : {"6.0.2600.2982", "5.82.2600.2982"}) { + const auto file = store->host / (std::string(architecture) + "_microsoft.windows.common-controls_6595b64144ccf1df_" + version + + "_none_deadbeef.manifest"); + if (!std::filesystem::exists(file, error)) + std::ofstream(file) << common_controls_manifest(architecture, version); + } + } + // The program's own manifest, resource 1 of type RT_MANIFEST, is the context in effect when no other is active. + const auto &image = context.entry; + const auto directory = image.image.directories[2]; + if (!directory.address || !directory.size || directory.size > 16 * 1024 * 1024) + return; + try { + const auto resources = read_resources(directory, context.process.read_memory(image.loaded.base() + directory.address, directory.size), + image.image.image_size); + for (const auto &resource : resources) + if (resource.type == ResourceKey(uint32_t(24)) && resource.name == ResourceKey(uint32_t(1)) && resource.size < 1024 * 1024) { + const auto bytes = context.process.read_memory(image.loaded.base() + resource.data_address, resource.size); + if (auto parsed = parse(std::string_view(reinterpret_cast(bytes.data()), bytes.size()), false)) + process_default = create(Context{std::move(*parsed), context.launch.image_path}); + break; + } + } catch (...) { + } +} + +std::optional ActivationContexts::parse(std::string_view bytes, bool) const +{ + const auto text = manifest_text(bytes); + Assembly root; + bool identified = false, in_dependency = false, in_file = false; + size_t at = 0; + while ((at = text.find('<', at)) != text.npos) { + if (text.compare(at, 4, "", at); + if (at == text.npos) + break; + continue; + } + const auto end = text.find('>', at); + if (end == text.npos) + break; + std::string_view tag(text.data() + at + 1, end - at - 1); + at = end + 1; + if (tag.empty() || tag[0] == '?' || tag[0] == '!') + continue; + const bool closing = tag[0] == '/'; + if (closing) + tag.remove_prefix(1); + const bool self_closing = !tag.empty() && tag.back() == '/'; + size_t name_end = 0; + while (name_end < tag.size() && tag[name_end] != ' ' && tag[name_end] != '\t' && tag[name_end] != '\r' && tag[name_end] != '\n' && + tag[name_end] != '/') + ++name_end; + auto element = lowered(tag.substr(0, name_end)); + if (const auto colon = element.find(':'); colon != element.npos) + element = element.substr(colon + 1); + // Attributes are name="value" or name='value'. + std::map attributes; + for (size_t scan = name_end; scan < tag.size();) { + while (scan < tag.size() && (tag[scan] == ' ' || tag[scan] == '\t' || tag[scan] == '\r' || tag[scan] == '\n')) + ++scan; + const auto equals = tag.find('=', scan); + if (equals == tag.npos) + break; + auto key = lowered(tag.substr(scan, equals - scan)); + while (!key.empty() && key.back() == ' ') + key.pop_back(); + size_t open = equals + 1; + while (open < tag.size() && tag[open] == ' ') + ++open; + if (open >= tag.size() || (tag[open] != '"' && tag[open] != '\'')) + break; + const auto close = tag.find(tag[open], open + 1); + if (close == tag.npos) + break; + attributes[key] = std::string(tag.substr(open + 1, close - open - 1)); + scan = close + 1; + } + if (element == "dependentassembly") + in_dependency = !closing && !self_closing; + else if (element == "file") { + in_file = !closing && !self_closing; + if (!closing && attributes.contains("name")) + root.files.push_back(attributes["name"]); + } else if (element == "assemblyidentity" && !closing) { + Assembly identity{attributes["name"], attributes["version"], lowered(attributes["processorarchitecture"]), lowered(attributes["publickeytoken"]), + {}, {}, {}}; + if (in_dependency) { + if (auto resolved = resolve(identity)) + root.dependencies.push_back(std::move(*resolved)); + } else if (!identified) { + identified = true; + root.name = identity.name; + root.version = identity.version; + root.architecture = identity.architecture; + root.token = identity.token; + } + } else if (element == "windowclass" && !closing && !self_closing && in_file && !in_dependency) { + const auto close = text.find('<', at); + if (close == text.npos) + break; + std::string name = text.substr(at, close - at); + while (!name.empty() && (name.back() == ' ' || name.back() == '\n' || name.back() == '\r' || name.back() == '\t')) + name.pop_back(); + while (!name.empty() && (name.front() == ' ' || name.front() == '\n' || name.front() == '\r' || name.front() == '\t')) + name.erase(name.begin()); + if (!name.empty()) + root.classes[lowered(name)] = lowered(attributes["versioned"]) != "no"; + } + } + if (!identified && root.dependencies.empty()) + return std::nullopt; + return root; +} + +// An assembly a manifest depends on: the manifest in the side-by-side store with its name, key and architecture, at the highest version +// that keeps the major version asked for, as the publisher policies of the system assemblies redirect. +std::optional ActivationContexts::resolve(const Assembly &identity) const +{ + if (!kernel || identity.name.empty()) + return std::nullopt; + const auto store = kernel->paths.resolve(u"C:\\Windows\\WinSxS\\Manifests"); + if (!store || !store->exists) + return std::nullopt; + const std::string architecture = identity.architecture.empty() || identity.architecture == "*" ? (wide_process ? "amd64" : "x86") : identity.architecture; + const auto wanted = version_parts(identity.version); + std::optional best; + std::error_code error; + for (const auto &entry : std::filesystem::directory_iterator(store->host, error)) { + const auto file = lowered(entry.path().filename().string()); + if (!file.ends_with(".manifest") || !file.starts_with(architecture + "_" + lowered(identity.name) + "_")) + continue; + std::ifstream input(entry.path(), std::ios::binary); + const std::string bytes((std::istreambuf_iterator(input)), std::istreambuf_iterator()); + auto candidate = parse(bytes, false); + if (!candidate || lowered(candidate->name) != lowered(identity.name) || (!identity.token.empty() && candidate->token != identity.token)) + continue; + const auto version = version_parts(candidate->version); + if (!wanted.empty() && wanted[0] && version[0] != wanted[0]) + continue; + if (!best || version > version_parts(best->version)) + best = std::move(candidate); + } + return best; +} + +uint64_t ActivationContexts::create(Context context) +{ + const auto handle = next_handle++; + contexts.emplace(handle, std::move(context)); + return handle; +} + +ActivationContexts::Context *ActivationContexts::find(uint64_t handle) +{ + const auto found = contexts.find(handle); + return found == contexts.end() ? nullptr : &found->second; +} + +void ActivationContexts::release(uint64_t handle) +{ + if (auto *context = find(handle); context && handle != process_default && !--context->references) + contexts.erase(handle); +} + +uint64_t ActivationContexts::current(uint32_t thread) const +{ + if (const auto found = stacks.find(thread); found != stacks.end() && !found->second.empty()) + return found->second.back().first; + return process_default; +} + +uint64_t ActivationContexts::activate(uint32_t thread, uint64_t handle) +{ + const auto cookie = next_cookie++; + stacks[thread].emplace_back(handle, cookie); + return cookie; +} + +bool ActivationContexts::deactivate(uint32_t thread, uint64_t cookie) +{ + auto &stack = stacks[thread]; + const auto found = std::find_if(stack.begin(), stack.end(), [&](const auto &frame) { return frame.second == cookie; }); + if (found == stack.end()) + return false; + stack.erase(found, stack.end()); + return true; +} + +std::optional ActivationContexts::registered_name(uint32_t thread, std::string_view name) const +{ + const auto found = stacks.find(thread); + if (found == stacks.end() || found->second.empty()) + return std::nullopt; + const auto context = contexts.find(found->second.back().first); + if (context == contexts.end()) + return std::nullopt; + const auto &root = context->second.root; + // Only version 6 and later carry their version in class names; the classes of earlier assemblies keep their plain names. + const auto declared = root.classes.find(lowered(name)); + if (declared == root.classes.end() || !declared->second || version_parts(root.version)[0] < 6) + return std::nullopt; + return root.version + "!" + std::string(name); +} + +std::optional ActivationContexts::lookup_name(uint32_t thread, std::string_view name) const +{ + const auto context = contexts.find(current(thread)); + if (context == contexts.end()) + return std::nullopt; + const auto key = lowered(name); + const auto in = [&](const Assembly &assembly) -> std::optional { + const auto declared = assembly.classes.find(key); + if (declared == assembly.classes.end() || !declared->second || version_parts(assembly.version)[0] < 6) + return std::nullopt; + return assembly.version + "!" + std::string(name); + }; + if (auto own = in(context->second.root)) + return own; + for (const auto &dependency : context->second.root.dependencies) + if (auto found = in(dependency)) + return found; + return std::nullopt; +} + +void register_actctx_api(ApiRegistry ®istry, KernelContext &context) +{ + auto &contexts = activation_contexts(); + contexts.attach(context); + const auto thread = [&context] { return context.threads.current_id(); }; + // CreateActCtxW(ACTCTXW): a manifest file, or a manifest resource of a module named by path or handle. + registry.add("CreateActCtxW", 1, [&context, &contexts](ApiCall &call) { + const bool narrow = call.pointer_width() == 4; + const size_t size = narrow ? 32 : 56; + if (!call[0]) { + call.fail(87, UINT64_MAX); + return; + } + const auto bytes = call.read(call[0], size); + const auto word = [&](size_t at, size_t width) { + uint64_t value = 0; + std::memcpy(&value, bytes.data() + at, width); + return value; + }; + const uint32_t flags = uint32_t(word(4, 4)); + const uint64_t source = word(narrow ? 8 : 8, narrow ? 4 : 8); + const uint64_t resource_name = word(narrow ? 20 : 32, narrow ? 4 : 8); + const uint64_t module = word(narrow ? 28 : 48, narrow ? 4 : 8); + std::string manifest; + std::u16string path = source ? call.wide(source, 1024) : std::u16string(); + const bool from_resource = (flags & 0x8) || (flags & 0x80); + if (from_resource) { + // The resource of a loaded module, read from its image; RT_MANIFEST with the name asked for, else the first one. + const PEModule *image = nullptr; + if (flags & 0x80) { + if (module == context.entry.loaded.base()) + image = &context.entry; + for (const auto &[name, loaded] : context.modules.all()) + if (!image && loaded->loaded.base() == module) + image = loaded.get(); + } else { + const auto file = path.substr(path.find_last_of(u"\\/") == path.npos ? 0 : path.find_last_of(u"\\/") + 1); + std::string key; + for (const auto character : file) + key.push_back(char(character < 128 ? std::tolower(int(character)) : '?')); + if (const auto found = context.modules.all().find(key); found != context.modules.all().end()) + image = found->second.get(); + else if (context.launch.image_path == path) + image = &context.entry; + } + if (image && image->image.directories[2].address) { + const auto directory = image->image.directories[2]; + const auto resources = read_resources(directory, call.read(image->loaded.base() + directory.address, directory.size), image->image.image_size); + for (const auto &resource : resources) { + if (resource.type != ResourceKey(uint32_t(24))) + continue; + if ((flags & 0x8) && resource_name <= 0xffff && resource.name != ResourceKey(uint32_t(resource_name))) + continue; + const auto data = call.read(image->loaded.base() + resource.data_address, resource.size); + manifest.assign(reinterpret_cast(data.data()), data.size()); + break; + } + } + } else if (const auto resolved = context.paths.resolve(path); resolved && resolved->exists) { + std::ifstream input(resolved->host, std::ios::binary); + manifest.assign(std::istreambuf_iterator(input), std::istreambuf_iterator()); + } else { + call.fail(2, UINT64_MAX); + return; + } + auto parsed = manifest.empty() ? std::nullopt : contexts.parse(manifest, false); + if (!parsed) { + call.fail(14001, UINT64_MAX); + return; + } + const auto handle = contexts.create({std::move(*parsed), path}); + if (flags & 0x10) + contexts.set_process_default(handle); + call.ret(handle); + }); + registry.add("AddRefActCtx", 1, [&contexts](ApiCall &call) { + if (auto *found = contexts.find(call[0])) + ++found->references; + }); + registry.add("ReleaseActCtx", 1, [&contexts](ApiCall &call) { contexts.release(call[0]); }); + registry.add("ZombifyActCtx", 1, [](ApiCall &call) { call.ret(1); }); + registry.add("ActivateActCtx", 2, [&contexts, thread](ApiCall &call) { + if (call[0] && call[0] != UINT64_MAX && !contexts.find(call[0])) { + call.fail(6); + return; + } + const auto cookie = contexts.activate(thread(), call[0] == UINT64_MAX ? 0 : call[0]); + if (call[1]) + call.store_pointer(call[1], cookie); + call.ret(1); + }); + registry.add("DeactivateActCtx", 2, [&contexts, thread](ApiCall &call) { + if (!contexts.deactivate(thread(), call[1]) && call[1]) { + call.fail(87); + return; + } + call.ret(1); + }); + registry.add("GetCurrentActCtx", 1, [&contexts, thread](ApiCall &call) { + if (!call[0]) { + call.fail(87); + return; + } + const auto handle = contexts.current(thread()); + if (auto *found = contexts.find(handle)) + ++found->references; + call.store_pointer(call[0], handle); + call.ret(1); + }); + // QueryActCtxW answers the basic information: the context and its flags. The detailed forms report that there is nothing to describe. + registry.add("QueryActCtxW", 7, [&contexts, thread](ApiCall &call) { + const uint32_t flags = call.u32(0), information = call.u32(3); + uint64_t handle = call[1]; + if (flags & 4) + handle = contexts.current(thread()); + if (information != 1) { + call.fail(87); + return; + } + const size_t width = call.pointer_width(), needed = width * 2; + if (call[6]) + call.store_pointer(call[6], needed); + if (!call[4] || call[5] < needed) { + call.fail(122); + return; + } + call.store_pointer(call[4], handle); + call.store_pointer(call[4] + width, 0); + call.ret(1); + }); + registry.add("FindActCtxSectionStringW", 5, [](ApiCall &call) { call.fail(14007); }); + registry.add("FindActCtxSectionGuid", 5, [](ApiCall &call) { call.fail(14007); }); +} + +} diff --git a/native/nt/actctx.h b/native/nt/actctx.h new file mode 100644 index 0000000..b335d85 --- /dev/null +++ b/native/nt/actctx.h @@ -0,0 +1,62 @@ +#pragma once + +#include "kernel_context.h" + +#include +#include +#include +#include +#include + +namespace raven { + +// Side-by-side activation contexts: the assemblies a manifest names, the libraries and window classes each one declares, and the stack of +// contexts every thread has activated. The process starts with the context of the program's own manifest, which is in effect whenever a +// thread has activated nothing else. +class ActivationContexts { +public: + struct Assembly { + std::string name, version, architecture, token; + // Window classes the assembly's libraries register, by lower-case name; true when the class name carries the version. + std::map classes; + std::vector files; + std::vector dependencies; + }; + struct Context { + Assembly root; + std::u16string source; + uint32_t references = 1; + }; + + // The runtime services the contexts need: guest memory, the prefix's paths and which thread is calling. + void attach(KernelContext &kernel); + // Parses a manifest's text; dependencies resolve through the WinSxS manifests of the prefix for the process's architecture. + std::optional parse(std::string_view text, bool wide) const; + uint64_t create(Context context); + Context *find(uint64_t handle); + void release(uint64_t handle); + // The context in effect on a thread: the last one it activated, else the program's own. + uint64_t current(uint32_t thread) const; + uint64_t activate(uint32_t thread, uint64_t handle); + bool deactivate(uint32_t thread, uint64_t cookie); + void set_process_default(uint64_t handle) { process_default = handle; } + uint64_t process_default_context() const { return process_default; } + // The name a class registers under while a thread has a context active: its version and an exclamation mark come first when the + // context's assembly declares the class. Lookups ask for the name an assembly the current context depends on gives it. + std::optional registered_name(uint32_t thread, std::string_view name) const; + std::optional lookup_name(uint32_t thread, std::string_view name) const; + std::function current_thread; + bool wide_process = true; + +private: + KernelContext *kernel = nullptr; + std::map contexts; + std::map>> stacks; + uint64_t next_handle = 0x7e000001, next_cookie = 1, process_default = 0; + std::optional resolve(const Assembly &identity) const; +}; + +ActivationContexts &activation_contexts(); +void register_actctx_api(ApiRegistry ®istry, KernelContext &context); + +} diff --git a/native/nt/process_api.cpp b/native/nt/process_api.cpp index 1bcd6ba..4ae7133 100644 --- a/native/nt/process_api.cpp +++ b/native/nt/process_api.cpp @@ -366,19 +366,6 @@ void register_process_api(ApiRegistry ®istry, KernelContext &context) for (const auto *name : {"GlobalGetAtomNameW", "GetAtomNameW"}) registry.add(name, 3, atom_name); - // Activation contexts only select side-by-side manifests; every program runs against one fixed set of libraries. - registry.add("CreateActCtxW", 1, [](ApiCall &call) { call.ret(0x7e000001); }); - registry.add("ActivateActCtx", 2, [](ApiCall &call) { - if (call[1]) - call.store(call[1], 1); - call.ret(1); - }); - registry.add("DeactivateActCtx", 2, [](ApiCall &call) { call.ret(1); }); - registry.add("ReleaseActCtx", 1, [](ApiCall &call) { call.ret(0); }); - registry.add("GetCurrentActCtx", 1, [](ApiCall &call) { - call.store(call[0], 0); - call.ret(1); - }); registry.add("IsBadReadPtr", 2, [](ApiCall &call) { call.ret(!accessible(call, call[0], call[1], false)); }); registry.add("IsBadWritePtr", 2, [](ApiCall &call) { call.ret(!accessible(call, call[0], call[1], true)); }); registry.add("IsBadCodePtr", 1, [](ApiCall &call) { call.ret(!accessible(call, call[0], 1, false)); }); diff --git a/native/nt/providers.cpp b/native/nt/providers.cpp index 5ea9183..6a144fd 100644 --- a/native/nt/providers.cpp +++ b/native/nt/providers.cpp @@ -15,6 +15,7 @@ #include "apiset.h" #include "text.h" #include "kernel_context.h" +#include "actctx.h" #include "unwind.h" #include "prefix.h" #include "profile_api.h" @@ -2037,6 +2038,7 @@ NativeResult run_native_process(ModuleSet &modules, PEModule &entry, register_shlwapi_api(api_registry, kernel); register_shell_api(api_registry, kernel); register_platform_api(api_registry, kernel); + register_actctx_api(api_registry, kernel); register_version_api(api_registry, kernel); windows.register_api(desktop_api); desktop.register_gdi_api(desktop_api); diff --git a/native/nt/x86_process.cpp b/native/nt/x86_process.cpp index 85f64bb..1855064 100644 --- a/native/nt/x86_process.cpp +++ b/native/nt/x86_process.cpp @@ -15,6 +15,7 @@ #include "guest_allocator.h" #include "heap.h" #include "kernel_context.h" +#include "actctx.h" #include "paths.h" #include "x86_compat_api.h" #include "registry_words.h" @@ -1070,6 +1071,7 @@ NativeResult run_native_process_x86(ModuleSet &modules, PEModule &entry, register_shell_api(shared_api, kernel); register_shlwapi_api(shared_api, kernel); register_platform_api(shared_api, kernel); + register_actctx_api(shared_api, kernel); register_process_api(shared_api, kernel); register_thread_api(shared_api, kernel); register_sync_api(shared_api, kernel); diff --git a/testdata/native_loader/actctx.manifest b/testdata/native_loader/actctx.manifest new file mode 100644 index 0000000..ba55c8c --- /dev/null +++ b/testdata/native_loader/actctx.manifest @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/testdata/native_loader/actctx.rc b/testdata/native_loader/actctx.rc new file mode 100644 index 0000000..14a68e5 --- /dev/null +++ b/testdata/native_loader/actctx.rc @@ -0,0 +1 @@ +1 24 "actctx.manifest" diff --git a/testdata/native_loader/actctx_client.c b/testdata/native_loader/actctx_client.c new file mode 100644 index 0000000..b208059 --- /dev/null +++ b/testdata/native_loader/actctx_client.c @@ -0,0 +1,77 @@ +#include +#include +#include + +/* Side-by-side classes and visual styles as a program with the common controls 6 manifest sees them: its buttons are the version 6 + class under their plain name, contexts made from a manifest file activate and deactivate, and the default button of the visual style + is drawn in the accent color. */ + +#ifndef ICC_STANDARD_CLASSES +#define ICC_STANDARD_CLASSES 0x4000 +#endif +/* The push button part and its default state, from the visual styles' part and state tables. */ +#define BP_PUSHBUTTON 1 +#define PBS_DEFAULTED 5 +#ifndef BCM_GETIDEALSIZE +#define BCM_GETIDEALSIZE 0x1601 +#endif + +int raven_entry(void) +{ + INITCOMMONCONTROLSEX classes = {sizeof classes, ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES}; + WCHAR name[64], path[MAX_PATH]; + if (!InitCommonControlsEx(&classes)) + return 1; + HWND button = CreateWindowExW(0, L"Button", L"OK", WS_POPUP | BS_PUSHBUTTON, 0, 0, 80, 24, NULL, NULL, GetModuleHandleW(NULL), NULL); + if (!button) + return 2; + if (!GetClassNameW(button, name, 64) || lstrcmpW(name, L"Button")) + return 3; + SIZE ideal = {0, 0}; + if (!SendMessageW(button, BCM_GETIDEALSIZE, 0, (LPARAM)&ideal)) + return 4; + DestroyWindow(button); + + GetModuleFileNameW(NULL, path, MAX_PATH); + for (WCHAR *at = path + lstrlenW(path); at > path; --at) + if (at[-1] == L'\\') { + lstrcpyW(at, L"actctx.manifest"); + break; + } + ACTCTXW request = {sizeof request}; + request.lpSource = path; + HANDLE context = CreateActCtxW(&request); + if (context == INVALID_HANDLE_VALUE) + return 5; + ULONG_PTR cookie = 0; + HANDLE current = NULL; + if (!ActivateActCtx(context, &cookie) || !cookie || !GetCurrentActCtx(¤t) || current != context) + return 6; + ReleaseActCtx(current); + if (!DeactivateActCtx(0, cookie)) + return 7; + ReleaseActCtx(context); + + if (!IsThemeActive()) + return 8; + HWND owner = CreateWindowExW(0, L"Button", L"", WS_POPUP, 0, 0, 10, 10, NULL, NULL, GetModuleHandleW(NULL), NULL); + HTHEME theme = OpenThemeData(owner, L"Button"); + if (!theme) + return 9; + BITMAPINFO info = {{sizeof(BITMAPINFOHEADER), 40, -20, 1, 32, BI_RGB}}; + void *bits = NULL; + HDC dc = CreateCompatibleDC(NULL); + HBITMAP bitmap = CreateDIBSection(dc, &info, DIB_RGB_COLORS, &bits, NULL, 0); + if (!bitmap || !bits) + return 10; + SelectObject(dc, bitmap); + RECT whole = {0, 0, 40, 20}; + if (DrawThemeBackground(theme, dc, BP_PUSHBUTTON, PBS_DEFAULTED, &whole, NULL) != S_OK) + return 11; + const BYTE *center = (const BYTE *)bits + (10 * 40 + 20) * 4; + if (center[0] != 0xb8 || center[1] != 0x5f || center[2] != 0x00) + return 12; + CloseThemeData(theme); + DestroyWindow(owner); + return 0; +}