mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Implement bounded registry APIs and opt-in platform configuration
This commit is contained in:
1 parent
9fe7ce1f3c
commit
b6302bb2c6
9 files changed
+1284
-19
No files matched your search
@@ -0,0 +1,73 @@
|
||||
# Explicit platform identity overrides
|
||||
|
||||
By default, Darling keeps the existing host-derived UUID and `DARLING-` serial.
|
||||
No override file is generated by installation or launch. This tool imports
|
||||
values supplied by the user; it does not generate Apple serials or replace OS,
|
||||
CPU, GPU, or security metadata.
|
||||
|
||||
Preview an OpenCore `config.plist`, a flat GenSMBIOS JSON file, or an existing
|
||||
`darling-smbios.conf` without changing the selected prefix:
|
||||
|
||||
```sh
|
||||
python3 tools/configure-darling-platform.py --prefix /absolute/prefix --import /path/to/config.plist
|
||||
```
|
||||
|
||||
After reviewing the preview, shut down that prefix and explicitly save:
|
||||
|
||||
```sh
|
||||
DPREFIX=/absolute/prefix python3 build/runtime/tools/stop-darling-runtime.py
|
||||
python3 tools/configure-darling-platform.py --prefix /absolute/prefix --import /path/to/config.plist --write
|
||||
```
|
||||
|
||||
The default prefix selection is `--prefix`, then `DPREFIX`, then saved
|
||||
`build/config.json`, then `~/.darling`. Writing refuses a live prefix. The
|
||||
configuration is activated when that prefix's iokitd starts again; it is an
|
||||
immutable startup snapshot. An absent or invalid configuration preserves the
|
||||
existing defaults. Invalid daemon-side configuration is reported once.
|
||||
|
||||
The file is `<prefix>/private/etc/darling-platform.plist`. It uses this schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"properties": {
|
||||
"IOPlatformSerialNumber": "USER-SUPPLIED",
|
||||
"model": "UserSuppliedModel"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported properties:
|
||||
|
||||
| Property | Configuration value | IOKit value |
|
||||
|---|---|---|
|
||||
| `IOPlatformSerialNumber` | Printable ASCII string, 1–64 characters | NSString |
|
||||
| `IOPlatformUUID` | Nonzero UUID; normalized to uppercase | NSString |
|
||||
| `manufacturer`, `model`, `product-name`, `board-id` | Printable ASCII string, 1–128 characters | NUL-terminated NSData |
|
||||
| `mlb-serial-number` | Printable ASCII string, 1–64 characters | NUL-terminated NSData |
|
||||
| `ROM` | Exactly 6 bytes; JSON accepts hexadecimal text | NSData |
|
||||
| `system-id` | Exactly 16 bytes; JSON accepts hexadecimal text | NSData |
|
||||
|
||||
Supplying a model also supplies the matching product name unless one is
|
||||
explicitly provided. Supplying a UUID derives its EFI little-endian `system-id`;
|
||||
a conflicting explicit system ID is rejected. No other missing fields are
|
||||
invented. These properties belong to the existing platform service; the tool
|
||||
does not manufacture a firmware/NVRAM registry hierarchy.
|
||||
|
||||
For OpenCore, the importer reads `PlatformInfo/Generic` (or a standalone Generic
|
||||
dictionary): `SystemProductName`, `SystemSerialNumber`, `SystemUUID`, `MLB`,
|
||||
and `ROM`. It ignores unrelated boot and security settings. GenSMBIOS aliases
|
||||
include `Type`, `Serial`, `Board Serial`, and `SmUUID`. Legacy `SMBIOS_*` identity
|
||||
keys are accepted from `.conf`; parsing never executes shell expressions.
|
||||
|
||||
Existing configured fields survive a partial import. A new explicit UUID
|
||||
replaces its previously derived system ID. `--set KEY=VALUE` can update an
|
||||
individual field. Unknown schema versions, unsupported properties, malformed
|
||||
values, and duplicate JSON fields are rejected.
|
||||
|
||||
Writes are atomic with mode 0600. Repeating the same write changes nothing. The
|
||||
first previous configuration is kept as
|
||||
`darling-platform.plist.before-platform-change`, also with mode 0600. Importing
|
||||
an older `.conf` never edits that source. Symlinks escaping the prefix are
|
||||
rejected. To restore the original defaults, remove the override file while the
|
||||
prefix is stopped, then restart it.
|
||||
@@ -25,6 +25,8 @@ from main after PRs #43 and #42. It does not claim to resolve account bans.
|
||||
the address of its input pointer. Hardware debug-state support remains
|
||||
unsupported.
|
||||
|
||||
The deferred compatibility groups are implemented in the [following stack layer](pr41-deferred-implementation.md).
|
||||
|
||||
## Work left outside this adaptation
|
||||
|
||||
The original PR's rendered source changes were inspected against its merge base,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# PR #41 deferred compatibility work
|
||||
|
||||
This follow-up is stacked on the compatibility extraction in PR #44. It
|
||||
implements the four groups deferred in that review, with explicit configuration
|
||||
and observable errors rather than automatic identity changes.
|
||||
|
||||
## Shell spawning
|
||||
|
||||
Diagnostics are quiet by default; `DARLING_SHELLSPAWN_DEBUG=1` enables the
|
||||
existing diagnostic output. The environment and arguments are no longer dumped
|
||||
on normal launches.
|
||||
|
||||
Stdio sources are duplicated before any standard descriptor is replaced, so
|
||||
aliases involving descriptors 0–2 are safe. Stream cleanup closes each owned
|
||||
descriptor once instead of repeatedly closing stream 0. Both fork branches drop
|
||||
unneeded descriptors, and error paths clean up the exec-status pipe. The
|
||||
control socket is kept out of the stdio slots. Terminal signal forwarding uses
|
||||
the remapped stdin. Unrelated descriptors and the configured shell selection
|
||||
are preserved.
|
||||
|
||||
## CPU frequency
|
||||
|
||||
`hw.cpufrequency` is an unsigned 64-bit frequency in Hz, matching XNU's named
|
||||
API. Linux cpufreq kHz values are checked for invalid input and overflow before
|
||||
conversion. The exported host sysfs location is tried as well as `/sys`; an
|
||||
available native CPUID base-frequency leaf is a fallback. No fixed CPU speed is
|
||||
invented when no source exists.
|
||||
|
||||
Numeric `HW_CPU_FREQ` retains the legacy 32-bit representation through a
|
||||
separate compatibility entry, matching XNU's distinction between the modern
|
||||
named API and the numeric compatibility API. Consumers needing frequencies
|
||||
above the 32-bit range should use the named 64-bit API.
|
||||
|
||||
## IOKit registry
|
||||
|
||||
- Inline and out-of-line path lookup validate termination, path shape, plane
|
||||
membership, and length. Paths resolve registered entries with case-sensitive
|
||||
names; no synthetic firmware nodes are inserted.
|
||||
- Path output uses inline storage when possible and a bounded sender buffer for
|
||||
long physical-copy replies. Incoming OOL mappings are released on success and
|
||||
error paths. The daemon's main queue serializes request processing and reply
|
||||
copying, so reply storage remains live through the send.
|
||||
- Plane membership follows registered ancestry. Deleting a parent or child
|
||||
removes both graph links. Plane-name keys own their strings, and cyclic
|
||||
registration is refused.
|
||||
- Raw property access handles strings, data, integer numbers at their reported
|
||||
widths, and XNU's `Yes`/`No` representation of booleans. It bounds writes by
|
||||
both the requested count and the fixed MIG array. Missing and unsupported
|
||||
values return errors; small buffers return IPC errors without writes.
|
||||
|
||||
## Platform configuration
|
||||
|
||||
[Platform identity configuration](../PLATFORM_IDENTITY.md) documents the schema,
|
||||
OpenCore/GenSMBIOS/legacy imports, startup snapshot, defaults, and rollback.
|
||||
Neither installation nor launch generates an override file. Existing host-based
|
||||
identifiers remain stable unless the user explicitly supplies replacements.
|
||||
|
||||
## Validation
|
||||
|
||||
Portable regression checks:
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests
|
||||
python3 scripts/prepare.py --check-only
|
||||
python3 scripts/validate.py
|
||||
python3 tools/riot-launcher-offline-check.py
|
||||
python3 tools/riot-input-offline-check.py
|
||||
python3 tools/riot-runtime-cleanup-offline-check.py
|
||||
```
|
||||
|
||||
The unit tests compile the actual patched fd helper and frequency code. They
|
||||
exercise fd aliases, unrelated descriptors, repeated failed remaps, conversion
|
||||
overflow, buffer widths, schema validation, imports, idempotency, backups,
|
||||
per-prefix isolation, malformed input, and preservation of existing identities.
|
||||
|
||||
With the source patches prepared, build an isolated iokitd candidate and probe:
|
||||
|
||||
```sh
|
||||
python3 build/runtime/tools/build-riot-registry-compatibility-check.py
|
||||
darling shell /Volumes/SystemRoot/ABSOLUTE_PROJECT_PATH/build/riot-registry-compatibility-check
|
||||
```
|
||||
|
||||
The probe uses the actual daemon objects and MIG handlers, and creates its own
|
||||
Mach ports. It does not register a daemon or replace the running iokitd. It
|
||||
checks traversal, deletion, cycles, malformed OOL input, 1,000 direct OOL
|
||||
lookups, 100 real IPC path round trips, property widths/bounds, and binary
|
||||
serialization round trips. It also checks daemon-side identity validation.
|
||||
|
||||
For a rebuilt kernel containing the frequency patch:
|
||||
|
||||
```sh
|
||||
python3 build/runtime/tools/build-riot-cpu-frequency-check.py
|
||||
darling shell /Volumes/SystemRoot/ABSOLUTE_PROJECT_PATH/build/riot-cpu-frequency-check
|
||||
```
|
||||
|
||||
Local validation also ran the fd helper inside Darling and used an isolated
|
||||
kernel override for the frequency probe. The configured production runtime was
|
||||
not replaced, no real identity was changed, and no match capture is required.
|
||||
+506
-17
@@ -26,10 +26,227 @@ index 84161fd..5b23944 100644
|
||||
src/IOSurface.mm
|
||||
src/IOUserClient.mm
|
||||
diff --git a/src/IORegistryEntry.mm b/src/IORegistryEntry.mm
|
||||
index 50c10fe..bd501cf 100644
|
||||
index 50c10fe..7026e1c 100644
|
||||
--- a/src/IORegistryEntry.mm
|
||||
+++ b/src/IORegistryEntry.mm
|
||||
@@ -513,3 +513,86 @@ kern_return_t is_io_registry_entry_get_property_bin
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "IORegistryEntry.h"
|
||||
#include <stack>
|
||||
#include <IOKit/IOCFSerialize.h>
|
||||
+#include <CoreFoundation/CFNumber.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sstream>
|
||||
+#include <limits>
|
||||
+#import <Foundation/NSData.h>
|
||||
+#import <Foundation/NSNumber.h>
|
||||
#include "IOIterator.h"
|
||||
extern "C" {
|
||||
#include "iokitmigServer.h"
|
||||
@@ -29,6 +33,12 @@ IORegistryEntry::~IORegistryEntry()
|
||||
{
|
||||
std::unique_lock l(m_entriesByIdMutex);
|
||||
m_entriesById.erase(m_id);
|
||||
+ // Registry requests and object teardown run on iokitd's main dispatch queue.
|
||||
+ // Detach both directions so a later traversal cannot follow a deleted entry.
|
||||
+ for (auto& plane : m_parents)
|
||||
+ for (auto* parent : plane.second) parent->m_children[plane.first].erase(this);
|
||||
+ for (auto& plane : m_children)
|
||||
+ for (auto* child : plane.second) child->m_parents[plane.first].erase(this);
|
||||
}
|
||||
|
||||
IORegistryEntry* IORegistryEntry::findById(uint64_t id)
|
||||
@@ -79,29 +89,42 @@ std::set<IORegistryEntry*> IORegistryEntry::getChildren(const char* plane)
|
||||
|
||||
void IORegistryEntry::registerInPlane(const char* plane, const char* pathName, IORegistryEntry* parent)
|
||||
{
|
||||
+ if (!plane || !*plane || !pathName || !*pathName || !parent || parent == this) return;
|
||||
+ std::set<IORegistryEntry*> visited;
|
||||
+ std::vector<IORegistryEntry*> pending { parent };
|
||||
+ while (!pending.empty()) {
|
||||
+ auto* current = pending.back(); pending.pop_back();
|
||||
+ if (current == this) return; // refuse cycles, including indirect ones
|
||||
+ if (!visited.insert(current).second) continue;
|
||||
+ for (auto* ancestor : current->getParents(plane)) pending.push_back(ancestor);
|
||||
+ }
|
||||
m_name[plane] = pathName;
|
||||
+ if (parent == root()) parent->m_name.emplace(plane, "Root");
|
||||
|
||||
auto it = m_parents.find(plane);
|
||||
|
||||
if (it == m_parents.end())
|
||||
- it = m_parents.insert(std::make_pair(std::string_view(plane), std::set<IORegistryEntry*>())).first;
|
||||
+ it = m_parents.insert(std::make_pair(std::string(plane), std::set<IORegistryEntry*>())).first;
|
||||
|
||||
it->second.insert(parent);
|
||||
|
||||
it = parent->m_children.find(plane);
|
||||
if (it == parent->m_children.end())
|
||||
- it = parent->m_children.insert(std::make_pair(std::string_view(plane), std::set<IORegistryEntry*>())).first;
|
||||
+ it = parent->m_children.insert(std::make_pair(std::string(plane), std::set<IORegistryEntry*>())).first;
|
||||
|
||||
it->second.insert(this);
|
||||
}
|
||||
|
||||
std::string IORegistryEntry::getPath(const char* plane)
|
||||
{
|
||||
+ if (!inPlane(plane)) return "?unsup_plane?";
|
||||
std::stack<std::string> components;
|
||||
+ std::set<IORegistryEntry*> visited;
|
||||
IORegistryEntry* cur = this;
|
||||
|
||||
while (true)
|
||||
{
|
||||
+ if (!visited.insert(cur).second) return "?unsup_plane?";
|
||||
std::set<IORegistryEntry*> parents = cur->getParents(plane);
|
||||
if (parents.empty())
|
||||
{
|
||||
@@ -113,18 +136,22 @@ std::string IORegistryEntry::getPath(const char* plane)
|
||||
|
||||
components.push(cur->getName(plane));
|
||||
|
||||
- IORegistryEntry* parent = *parents.begin();
|
||||
+ IORegistryEntry* parent = nullptr;
|
||||
+ for (auto* candidate : parents) if (candidate->inPlane(plane)) { parent = candidate; break; }
|
||||
+ if (!parent) return "?unsup_plane?";
|
||||
cur = parent;
|
||||
}
|
||||
|
||||
std::string path = plane;
|
||||
path += ":";
|
||||
+ if (components.empty()) path += "/";
|
||||
|
||||
while (!components.empty())
|
||||
{
|
||||
path += "/";
|
||||
path += components.top();
|
||||
components.pop();
|
||||
+ if (path.size() > 65535) return "?unsup_plane?";
|
||||
}
|
||||
|
||||
return path;
|
||||
@@ -166,11 +193,15 @@ kern_return_t is_io_registry_entry_get_path
|
||||
io_string_t path
|
||||
)
|
||||
{
|
||||
+ if (!path) return kIOReturnBadArgument;
|
||||
IORegistryEntry* e = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
||||
if (!e)
|
||||
return kIOReturnBadArgument;
|
||||
|
||||
+ if (!plane || !memchr(plane, 0, sizeof(io_name_t)) || !e->inPlane(plane)) return kIOReturnBadArgument;
|
||||
std::string mypath = e->getPath(plane);
|
||||
+ if (mypath == "?unsup_plane?") return kIOReturnBadArgument;
|
||||
+ if (mypath.size() >= sizeof(io_string_t)) { path[0] = 0; return kIOReturnNoSpace; }
|
||||
strlcpy(path, mypath.c_str(), sizeof(io_string_t));
|
||||
|
||||
return kIOReturnSuccess;
|
||||
@@ -386,55 +417,59 @@ kern_return_t is_io_registry_entry_get_location_in_plane
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
-kern_return_t is_io_registry_entry_from_path
|
||||
-(
|
||||
- mach_port_t master_port,
|
||||
- io_string_t path,
|
||||
- mach_port_t *registry_entry
|
||||
-)
|
||||
+bool IORegistryEntry::inPlane(const char* plane) const
|
||||
{
|
||||
- *registry_entry = 0;
|
||||
-
|
||||
- std::vector<std::string> components;
|
||||
- std::istringstream iss(path);
|
||||
-
|
||||
- for (std::string token; std::getline(iss, token, '/'); )
|
||||
- {
|
||||
- if (!token.empty())
|
||||
- components.push_back(std::move(token));
|
||||
+ if (!plane || !*plane || m_name.find(plane) == m_name.end()) return false;
|
||||
+ std::set<const IORegistryEntry*> visited;
|
||||
+ std::vector<const IORegistryEntry*> pending { this };
|
||||
+ while (!pending.empty()) {
|
||||
+ auto* current = pending.back(); pending.pop_back();
|
||||
+ if (current == root()) return true;
|
||||
+ if (!visited.insert(current).second) continue;
|
||||
+ auto parents = current->m_parents.find(plane);
|
||||
+ if (parents != current->m_parents.end())
|
||||
+ for (auto* parent : parents->second) pending.push_back(parent);
|
||||
}
|
||||
-
|
||||
- if (components.empty())
|
||||
- return kIOReturnBadArgument;
|
||||
-
|
||||
- std::string& planeName = components[0];
|
||||
- if (planeName[planeName.length()-1] != ':')
|
||||
- return kIOReturnBadArgument;
|
||||
- else
|
||||
- planeName.resize(planeName.length()-1); // remove the final colon
|
||||
-
|
||||
- IORegistryEntry* current = IORegistryEntry::root();
|
||||
- for (size_t i = 1; i < components.size(); i++)
|
||||
- {
|
||||
- auto children = current->getChildren(planeName.c_str());
|
||||
- IORegistryEntry* next = nullptr;
|
||||
+ return false;
|
||||
+}
|
||||
|
||||
- for (IORegistryEntry* e : children)
|
||||
- {
|
||||
- if (strcasecmp(e->getName(planeName.c_str()).c_str(), components[i].c_str()) == 0)
|
||||
- {
|
||||
- next = e;
|
||||
- break;
|
||||
- }
|
||||
+IORegistryEntry* IORegistryEntry::fromPath(std::string_view path)
|
||||
+{
|
||||
+ if (path.empty() || path.size() > 65535 || path.find('\0') != std::string_view::npos) return nullptr;
|
||||
+ auto colon = path.find(':');
|
||||
+ if (colon == 0 || colon == std::string_view::npos || colon >= sizeof(io_name_t)) return nullptr;
|
||||
+ std::string plane(path.substr(0, colon));
|
||||
+ if (plane.find('/') != std::string::npos) return nullptr;
|
||||
+ auto* current = root();
|
||||
+ if (!current->inPlane(plane.c_str())) return nullptr;
|
||||
+ path.remove_prefix(colon + 1);
|
||||
+ if (!path.empty() && path.front() != '/') return nullptr;
|
||||
+ while (!path.empty()) {
|
||||
+ path.remove_prefix(1);
|
||||
+ auto slash = path.find('/');
|
||||
+ auto name = path.substr(0, slash);
|
||||
+ if (!name.empty()) {
|
||||
+ if (name == "." || name == "..") return nullptr;
|
||||
+ IORegistryEntry* next = nullptr;
|
||||
+ for (auto* child : current->getChildren(plane.c_str()))
|
||||
+ if (child->getName(plane.c_str()) == name) { next = child; break; }
|
||||
+ if (!next) return nullptr;
|
||||
+ current = next;
|
||||
}
|
||||
-
|
||||
- if (!next)
|
||||
- return kIOReturnNoDevice;
|
||||
- current = next;
|
||||
+ if (slash == std::string_view::npos) break;
|
||||
+ path.remove_prefix(slash);
|
||||
}
|
||||
+ return current; // borrowed until this main-queue request completes
|
||||
+}
|
||||
|
||||
- *registry_entry = current->port();
|
||||
-
|
||||
+kern_return_t is_io_registry_entry_from_path(mach_port_t master_port, io_string_t path, mach_port_t* registry_entry)
|
||||
+{
|
||||
+ if (!registry_entry) return kIOReturnBadArgument;
|
||||
+ *registry_entry = MACH_PORT_NULL;
|
||||
+ if (!path || !memchr(path, 0, sizeof(io_string_t))) return kIOReturnBadArgument;
|
||||
+ auto* entry = IORegistryEntry::fromPath(path);
|
||||
+ if (!entry) return kIOReturnNoDevice;
|
||||
+ *registry_entry = entry->port();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
@@ -513,3 +548,189 @@ kern_return_t is_io_registry_entry_get_property_bin
|
||||
return kr;
|
||||
}
|
||||
|
||||
@@ -116,6 +333,109 @@ index 50c10fe..bd501cf 100644
|
||||
+ CFRelease(data);
|
||||
+ return kr;
|
||||
+}
|
||||
+
|
||||
+// OOL input ownership transfers to this server; release it on every exit path.
|
||||
+kern_return_t is_io_registry_entry_from_path_ool(mach_port_t master_port, io_string_inband_t path,
|
||||
+ io_buf_ptr_t path_ool, mach_msg_type_number_t path_oolCnt, kern_return_t* result, mach_port_t* registry_entry)
|
||||
+{
|
||||
+ struct Input {
|
||||
+ io_buf_ptr_t bytes; mach_msg_type_number_t size;
|
||||
+ ~Input() { if (bytes && size) vm_deallocate(mach_task_self(), (vm_address_t)bytes, size); }
|
||||
+ } input {path_ool, path_oolCnt};
|
||||
+ if (!result || !registry_entry) return kIOReturnBadArgument;
|
||||
+ *registry_entry = MACH_PORT_NULL;
|
||||
+ *result = kIOReturnBadArgument;
|
||||
+ std::string_view fullPath;
|
||||
+ if (path_oolCnt || path_ool) {
|
||||
+ if (!path_ool || !path_oolCnt || path_oolCnt > 65536 || !path || path[0] ||
|
||||
+ strnlen(path_ool, path_oolCnt) != path_oolCnt - 1) return kIOReturnSuccess;
|
||||
+ fullPath = std::string_view(path_ool, path_oolCnt - 1);
|
||||
+ } else {
|
||||
+ if (!path || !memchr(path, 0, sizeof(io_string_inband_t))) return kIOReturnSuccess;
|
||||
+ fullPath = path;
|
||||
+ }
|
||||
+ auto* entry = IORegistryEntry::fromPath(fullPath);
|
||||
+ if (entry) { *registry_entry = entry->port(); *result = kIOReturnSuccess; }
|
||||
+ else *result = kIOReturnNoDevice;
|
||||
+ return kIOReturnSuccess;
|
||||
+}
|
||||
+
|
||||
+kern_return_t is_io_registry_entry_get_path_ool(mach_port_t registry_entry, io_name_t plane,
|
||||
+ io_string_inband_t path, io_buf_ptr_t* path_ool, mach_msg_type_number_t* path_oolCnt)
|
||||
+{
|
||||
+ if (!path || !path_ool || !path_oolCnt) return kIOReturnBadArgument;
|
||||
+ path[0] = 0; *path_ool = nullptr; *path_oolCnt = 0;
|
||||
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
||||
+ if (!entry || !plane || !memchr(plane, 0, sizeof(io_name_t)) || !entry->inPlane(plane)) return kIOReturnBadArgument;
|
||||
+ auto value = entry->getPath(plane);
|
||||
+ if (value == "?unsup_plane?") return kIOReturnBadArgument;
|
||||
+ if (value.size() < sizeof(io_string_inband_t)) {
|
||||
+ strlcpy(path, value.c_str(), sizeof(io_string_inband_t));
|
||||
+ } else {
|
||||
+ // MIG uses physicalcopy with deallocate=false. The main queue completes
|
||||
+ // each reply before its next request, so one bounded reusable buffer
|
||||
+ // keeps the sender's bytes alive without leaking one VM allocation/call.
|
||||
+ static std::string reply;
|
||||
+ reply.assign(value);
|
||||
+ *path_ool = const_cast<char*>(reply.data());
|
||||
+ *path_oolCnt = reply.size(); // IOKit creates a CFString from bytes, excluding NUL
|
||||
+ }
|
||||
+ return kIOReturnSuccess;
|
||||
+}
|
||||
+
|
||||
+kern_return_t is_io_registry_entry_in_plane(mach_port_t registry_entry, io_name_t plane, boolean_t* inPlane)
|
||||
+{
|
||||
+ if (!inPlane) return kIOReturnBadArgument;
|
||||
+ *inPlane = FALSE;
|
||||
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
||||
+ if (!entry || !plane || !memchr(plane, 0, sizeof(io_name_t))) return kIOReturnBadArgument;
|
||||
+ *inPlane = entry->inPlane(plane);
|
||||
+ return kIOReturnSuccess;
|
||||
+}
|
||||
+
|
||||
+kern_return_t is_io_registry_entry_get_property_bytes(mach_port_t registry_entry, io_name_t property_name,
|
||||
+ io_struct_inband_t data, mach_msg_type_number_t* dataCnt)
|
||||
+{
|
||||
+ if (!dataCnt) return kIOReturnBadArgument;
|
||||
+ size_t capacity = std::min<size_t>(*dataCnt, sizeof(io_struct_inband_t));
|
||||
+ *dataCnt = 0;
|
||||
+ auto* entry = dynamic_cast<IORegistryEntry*>(IOObject::lookup(registry_entry));
|
||||
+ if (!entry || !property_name || !memchr(property_name, 0, sizeof(io_name_t))) return kIOReturnBadArgument;
|
||||
+ NSString* key = [NSString stringWithUTF8String:property_name];
|
||||
+ if (!key) return kIOReturnBadArgument;
|
||||
+ id value = [entry->getProperties() objectForKey:key];
|
||||
+ if (!value) return kIOReturnNoResources;
|
||||
+ const void* bytes = nullptr;
|
||||
+ size_t length = 0;
|
||||
+ uint64_t number = 0;
|
||||
+ if ([value isKindOfClass:[NSData class]]) {
|
||||
+ bytes = [value bytes]; length = [value length];
|
||||
+ } else if ([value isKindOfClass:[NSString class]]) {
|
||||
+ bytes = [value UTF8String];
|
||||
+ if (!bytes) return kIOReturnBadArgument;
|
||||
+ length = strlen((const char*)bytes) + 1;
|
||||
+ } else if ([value isKindOfClass:[NSNumber class]] && CFGetTypeID((CFTypeRef)value) == CFBooleanGetTypeID()) {
|
||||
+ bytes = [value boolValue] ? "Yes" : "No";
|
||||
+ length = [value boolValue] ? sizeof("Yes") : sizeof("No");
|
||||
+ } else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
+ const char* type = [value objCType];
|
||||
+ switch (type ? type[0] : 0) {
|
||||
+ case 'c': case 'C': case 'B': length = 1; break;
|
||||
+ case 's': case 'S': length = 2; break;
|
||||
+ case 'i': case 'I': length = 4; break;
|
||||
+ case 'l': case 'L': length = sizeof(long); break;
|
||||
+ case 'q': case 'Q': length = 8; break;
|
||||
+ default: return kIOReturnBadArgument;
|
||||
+ }
|
||||
+ number = [value unsignedLongLongValue]; bytes = &number;
|
||||
+ } else return kIOReturnBadArgument;
|
||||
+ if (length > std::numeric_limits<mach_msg_type_number_t>::max()) return kIOReturnNoSpace;
|
||||
+ if (length > capacity) { *dataCnt = capacity; return kIOReturnIPCError; }
|
||||
+ *dataCnt = length;
|
||||
+ if (length && !data) { *dataCnt = 0; return kIOReturnBadArgument; }
|
||||
+ if (length) memcpy(data, bytes, length);
|
||||
+ return kIOReturnSuccess;
|
||||
+}
|
||||
diff --git a/src/IOSurfaceRoot.mm b/src/IOSurfaceRoot.mm
|
||||
index d7e6fab..d860582 100644
|
||||
--- a/src/IOSurfaceRoot.mm
|
||||
@@ -503,13 +823,25 @@ index a568ecc..c2d4063 100644
|
||||
/* Routine io_ps_copy_chargelog */
|
||||
|
||||
diff --git a/src/stubs.c b/src/stubs.c
|
||||
index 91d11a1..37551e1 100644
|
||||
index 91d11a1..5a00091 100644
|
||||
--- a/src/stubs.c
|
||||
+++ b/src/stubs.c
|
||||
@@ -45,15 +45,6 @@ kern_return_t is_io_registry_entry_get_property_bytes
|
||||
@@ -33,26 +33,7 @@ kern_return_t is_io_registry_iterator_exit_entry
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
-kern_return_t is_io_registry_entry_get_property_bytes
|
||||
-(
|
||||
- mach_port_t registry_entry,
|
||||
- io_name_t property_name,
|
||||
- io_struct_inband_t data,
|
||||
- mach_msg_type_number_t *dataCnt
|
||||
-)
|
||||
-{
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
|
||||
-kern_return_t is_io_service_close
|
||||
-(
|
||||
- mach_port_t connection
|
||||
@@ -518,11 +850,28 @@ index 91d11a1..37551e1 100644
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
-
|
||||
|
||||
kern_return_t is_io_connect_get_service
|
||||
(
|
||||
mach_port_t connection,
|
||||
@@ -325,20 +316,6 @@ kern_return_t is_io_service_add_notification
|
||||
@@ -124,16 +105,7 @@ kern_return_t is_io_registry_entry_set_properties
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
-kern_return_t is_io_registry_entry_in_plane
|
||||
-(
|
||||
- mach_port_t registry_entry,
|
||||
- io_name_t plane,
|
||||
- boolean_t *inPlane
|
||||
-)
|
||||
-{
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
+
|
||||
|
||||
kern_return_t is_io_service_get_busy_state
|
||||
(
|
||||
@@ -325,20 +297,6 @@ kern_return_t is_io_service_add_notification
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -543,7 +892,7 @@ index 91d11a1..37551e1 100644
|
||||
kern_return_t is_io_service_acknowledge_notification
|
||||
(
|
||||
mach_port_t service,
|
||||
@@ -439,22 +416,6 @@ kern_return_t is_io_object_get_bundle_identifier
|
||||
@@ -439,22 +397,6 @@ kern_return_t is_io_object_get_bundle_identifier
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -566,7 +915,7 @@ index 91d11a1..37551e1 100644
|
||||
kern_return_t is_io_connect_map_memory_into_task
|
||||
(
|
||||
mach_port_t connection,
|
||||
@@ -533,20 +494,6 @@ kern_return_t is_io_service_add_notification_64
|
||||
@@ -533,20 +475,6 @@ kern_return_t is_io_service_add_notification_64
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -587,7 +936,7 @@ index 91d11a1..37551e1 100644
|
||||
kern_return_t is_io_service_add_notification_ool_64
|
||||
(
|
||||
mach_port_t master_port,
|
||||
@@ -642,18 +589,6 @@ kern_return_t is_io_server_version
|
||||
@@ -642,18 +570,6 @@ kern_return_t is_io_server_version
|
||||
}
|
||||
|
||||
|
||||
@@ -606,7 +955,7 @@ index 91d11a1..37551e1 100644
|
||||
kern_return_t is_io_service_match_property_table_bin
|
||||
(
|
||||
mach_port_t service,
|
||||
@@ -682,22 +617,6 @@ kern_return_t is_io_service_add_notification_bin
|
||||
@@ -682,48 +598,9 @@ kern_return_t is_io_service_add_notification_bin
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -625,11 +974,38 @@ index 91d11a1..37551e1 100644
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
-
|
||||
kern_return_t is_io_registry_entry_from_path_ool
|
||||
|
||||
-kern_return_t is_io_registry_entry_from_path_ool
|
||||
-(
|
||||
- mach_port_t master_port,
|
||||
- io_string_inband_t path,
|
||||
- io_buf_ptr_t path_ool,
|
||||
- mach_msg_type_number_t path_oolCnt,
|
||||
- kern_return_t *result,
|
||||
- mach_port_t *registry_entry
|
||||
-)
|
||||
-{
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
|
||||
-kern_return_t is_io_registry_entry_get_path_ool
|
||||
- (
|
||||
- mach_port_t registry_entry,
|
||||
- io_name_t plane,
|
||||
- io_string_inband_t path,
|
||||
- io_buf_ptr_t *path_ool,
|
||||
- mach_msg_type_number_t *path_oolCnt
|
||||
-)
|
||||
-{
|
||||
- STUB();
|
||||
- return KERN_NOT_SUPPORTED;
|
||||
-}
|
||||
+
|
||||
|
||||
kern_return_t is_io_device_tree_entry_exists_with_name
|
||||
(
|
||||
mach_port_t master_port,
|
||||
@@ -736,31 +655,3 @@ kern_return_t is_io_device_tree_entry_exists_with_name
|
||||
@@ -736,31 +613,3 @@ kern_return_t is_io_device_tree_entry_exists_with_name
|
||||
return KERN_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -690,11 +1066,12 @@ index 0000000..38ad635
|
||||
+#endif
|
||||
diff --git a/src/IOPlatformExpertDevice.mm b/src/IOPlatformExpertDevice.mm
|
||||
new file mode 100644
|
||||
index 0000000..e330569
|
||||
index 0000000..71b9f4d
|
||||
--- /dev/null
|
||||
+++ b/src/IOPlatformExpertDevice.mm
|
||||
@@ -0,0 +1,93 @@
|
||||
@@ -0,0 +1,98 @@
|
||||
+#include "IOPlatformExpertDevice.h"
|
||||
+#include "PlatformIdentity.h"
|
||||
+#import <Foundation/NSString.h>
|
||||
+#import <Foundation/NSData.h>
|
||||
+#import <Foundation/NSArray.h>
|
||||
@@ -770,6 +1147,10 @@ index 0000000..e330569
|
||||
+ properties[@"IOPlatformUUID"] = uuid;
|
||||
+ properties[@"IOPlatformSerialNumber"] = serial;
|
||||
+ }
|
||||
+ // Opt-in, immutable startup snapshot. No configuration preserves the existing
|
||||
+ // per-host identifiers; daemon restart is the explicit activation boundary.
|
||||
+ static NSDictionary* overrides = [DarlingPlatformIdentity::load("/private/etc/darling-platform.plist") copy];
|
||||
+ if (overrides) [properties addEntriesFromDictionary:overrides];
|
||||
+ return properties;
|
||||
+}
|
||||
+
|
||||
@@ -3115,3 +3496,111 @@ index 7a6fcfa..26a7d68 100644
|
||||
+ m_changedDPMS = false;
|
||||
}
|
||||
}
|
||||
diff --git a/src/IORegistryEntry.h b/src/IORegistryEntry.h
|
||||
index 90155ed..e955bd4 100644
|
||||
--- a/src/IORegistryEntry.h
|
||||
+++ b/src/IORegistryEntry.h
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
|
||||
std::string_view getPathName(const char* plane);
|
||||
std::string getPath(const char* plane);
|
||||
+ bool inPlane(const char* plane) const;
|
||||
+ static IORegistryEntry* fromPath(std::string_view path);
|
||||
std::set<IORegistryEntry*> getParents(const char* plane);
|
||||
std::set<IORegistryEntry*> getChildren(const char* plane);
|
||||
std::string getName(const char* plane = nullptr);
|
||||
@@ -32,8 +34,8 @@ public:
|
||||
void registerInPlane(const char* plane, const char* pathName, IORegistryEntry* parent);
|
||||
protected:
|
||||
// The key is the plane name, e.g. kIOServicePlane (as declared in IOKit/IOKitKeys.h)
|
||||
- std::unordered_map<std::string_view, std::string> m_name;
|
||||
- std::unordered_map<std::string_view, std::set<IORegistryEntry*>> m_parents, m_children;
|
||||
+ std::unordered_map<std::string, std::string> m_name;
|
||||
+ std::unordered_map<std::string, std::set<IORegistryEntry*>> m_parents, m_children;
|
||||
uint64_t m_id;
|
||||
|
||||
static std::unordered_map<uint64_t, IORegistryEntry*> m_entriesById;
|
||||
diff --git a/src/PlatformIdentity.h b/src/PlatformIdentity.h
|
||||
new file mode 100644
|
||||
index 0000000..1f83b65
|
||||
--- /dev/null
|
||||
+++ b/src/PlatformIdentity.h
|
||||
@@ -0,0 +1,78 @@
|
||||
+#ifndef DARLING_PLATFORM_IDENTITY_H
|
||||
+#define DARLING_PLATFORM_IDENTITY_H
|
||||
+#import <Foundation/Foundation.h>
|
||||
+#include <uuid/uuid.h>
|
||||
+#include <cstdio>
|
||||
+#include <cstring>
|
||||
+#include <array>
|
||||
+
|
||||
+namespace DarlingPlatformIdentity {
|
||||
+inline bool printable(id value, size_t maximum) {
|
||||
+ if (![value isKindOfClass:[NSString class]]) return false;
|
||||
+ const char* text = [value UTF8String];
|
||||
+ if (!text) return false;
|
||||
+ size_t length = strlen(text);
|
||||
+ if (!length || length > maximum || length != [value length]) return false;
|
||||
+ for (size_t i = 0; i < length; ++i) if ((unsigned char)text[i] < 32 || (unsigned char)text[i] > 126) return false;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+// Validate the entire schema before exposing any overrides. Strings on device
|
||||
+// tree properties are NUL-terminated NSData; serial/UUID retain NSString types.
|
||||
+inline NSDictionary* properties(id configuration) {
|
||||
+ if (![configuration isKindOfClass:[NSDictionary class]] || [configuration count] != 2) return nil;
|
||||
+ id schema = [configuration objectForKey:@"schema"];
|
||||
+ if (![schema isKindOfClass:[NSNumber class]] || CFGetTypeID((CFTypeRef)schema) == CFBooleanGetTypeID() ||
|
||||
+ strchr("fd", [schema objCType][0]) || [schema longLongValue] != 1) return nil;
|
||||
+ id source = [configuration objectForKey:@"properties"];
|
||||
+ if (![source isKindOfClass:[NSDictionary class]] || [source count] > 9) return nil;
|
||||
+ NSMutableDictionary* result = [NSMutableDictionary dictionary];
|
||||
+ for (id key in source) {
|
||||
+ if (![key isKindOfClass:[NSString class]]) return nil;
|
||||
+ id value = [source objectForKey:key];
|
||||
+ if ([key isEqual:@"IOPlatformUUID"]) {
|
||||
+ if (!printable(value, 36) || [value length] != 36) return nil;
|
||||
+ uuid_t uuid;
|
||||
+ if (uuid_parse([value UTF8String], uuid) || uuid_is_null(uuid)) return nil;
|
||||
+ uuid_string_t text; uuid_unparse_upper(uuid, text);
|
||||
+ result[key] = [NSString stringWithUTF8String:text];
|
||||
+ unsigned char bytes[16]; memcpy(bytes, uuid, sizeof(bytes));
|
||||
+ for (int i = 0; i < 4; ++i) bytes[i] = uuid[3-i];
|
||||
+ bytes[4] = uuid[5]; bytes[5] = uuid[4]; bytes[6] = uuid[7]; bytes[7] = uuid[6];
|
||||
+ NSData* systemID = [NSData dataWithBytes:bytes length:16];
|
||||
+ if (source[@"system-id"] && ![source[@"system-id"] isEqual:systemID]) return nil;
|
||||
+ result[@"system-id"] = systemID;
|
||||
+ } else if ([key isEqual:@"ROM"] || [key isEqual:@"system-id"]) {
|
||||
+ if (![value isKindOfClass:[NSData class]] || [value length] != ([key isEqual:@"ROM"] ? 6 : 16)) return nil;
|
||||
+ result[key] = value;
|
||||
+ } else if ([key isEqual:@"IOPlatformSerialNumber"]) {
|
||||
+ if (!printable(value, 64)) return nil;
|
||||
+ result[key] = value;
|
||||
+ } else if ([key isEqual:@"manufacturer"] || [key isEqual:@"model"] || [key isEqual:@"product-name"] ||
|
||||
+ [key isEqual:@"board-id"] || [key isEqual:@"mlb-serial-number"]) {
|
||||
+ if (!printable(value, [key isEqual:@"mlb-serial-number"] ? 64 : 128)) return nil;
|
||||
+ const char* text = [value UTF8String];
|
||||
+ result[key] = [NSData dataWithBytes:text length:strlen(text)+1];
|
||||
+ } else return nil;
|
||||
+ }
|
||||
+ if (result[@"model"] && !result[@"product-name"]) result[@"product-name"] = result[@"model"];
|
||||
+ return result;
|
||||
+}
|
||||
+
|
||||
+inline NSDictionary* load(const char* path) {
|
||||
+ FILE* file = fopen(path, "rb");
|
||||
+ if (!file) return nil; // no overrides is the normal, unchanged default
|
||||
+ std::array<unsigned char, 65537> buffer;
|
||||
+ size_t size = fread(buffer.data(), 1, buffer.size(), file);
|
||||
+ bool failed = ferror(file); fclose(file);
|
||||
+ if (!failed && size <= 65536) {
|
||||
+ NSData* data = [NSData dataWithBytes:buffer.data() length:size];
|
||||
+ id config = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL];
|
||||
+ NSDictionary* overrides = properties(config);
|
||||
+ if (overrides) return overrides;
|
||||
+ }
|
||||
+ fprintf(stderr, "iokitd: invalid darling-platform.plist; retaining default platform properties\n");
|
||||
+ return nil;
|
||||
+}
|
||||
+}
|
||||
+#endif
|
||||
@@ -0,0 +1,157 @@
|
||||
"""Disposable-prefix checks for explicit platform identity configuration."""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import uuid
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / 'tools/configure-darling-platform.py'
|
||||
spec = importlib.util.spec_from_file_location('platform_config', SCRIPT)
|
||||
platform = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(platform)
|
||||
UUID = '00112233-4455-4677-8899-AABBCCDDEEFF'
|
||||
|
||||
|
||||
class PlatformIdentityChecks(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
self.root = Path(self.temporary.name)
|
||||
self.prefix = self.root / 'prefix with spaces'
|
||||
|
||||
def source(self, name, data):
|
||||
path = self.root / name
|
||||
path.write_bytes(data)
|
||||
return path
|
||||
|
||||
def test_opencore_import_and_uuid_byte_order(self):
|
||||
source = self.source('OpenCore.plist', plistlib.dumps({'PlatformInfo': {'Generic': {
|
||||
'SystemProductName': 'ExampleModel1,1', 'SystemSerialNumber': 'MY-SERIAL',
|
||||
'SystemUUID': UUID, 'MLB': 'MY-BOARD', 'ROM': bytes.fromhex('0123456789AB'),
|
||||
'SpoofVendor': True}}}))
|
||||
result = platform.load(source)
|
||||
self.assertEqual(result['model'], result['product-name'])
|
||||
self.assertEqual(result['IOPlatformUUID'], UUID)
|
||||
self.assertEqual(result['system-id'].hex().upper(), '33221100554477468899AABBCCDDEEFF')
|
||||
self.assertEqual(result['ROM'], bytes.fromhex('0123456789AB'))
|
||||
self.assertNotIn('SpoofVendor', result)
|
||||
|
||||
def test_gensmbios_partial_import_does_not_generate_missing_identity(self):
|
||||
source = self.source('identity.json', json.dumps({'Type': 'ExampleModel1,1', 'Serial': 'SUPPLIED'}).encode())
|
||||
result = platform.load(source)
|
||||
self.assertEqual(set(result), {'model', 'product-name', 'IOPlatformSerialNumber'})
|
||||
self.assertNotIn('IOPlatformUUID', result)
|
||||
|
||||
def test_invalid_values_are_rejected(self):
|
||||
for properties in [
|
||||
{'IOPlatformUUID': 'not-a-uuid'}, {'IOPlatformUUID': str(uuid.UUID(int=0))},
|
||||
{'IOPlatformUUID': UUID, 'system-id': bytes(16)}, {'ROM': bytes(7)},
|
||||
{'ROM': 'xyz'}, {'model': 'bad\x00name'}, {'model': 'bad\nname'},
|
||||
{'model': 'é'}, {'model': 'X'*129}, {'IOPlatformSerialNumber': ''},
|
||||
{'IOPlatformSerialNumber': 123}, {'unknown': 'value'}, {'ROM': True},
|
||||
]:
|
||||
with self.subTest(properties=properties), self.assertRaises((ValueError, TypeError)):
|
||||
platform.validate(properties)
|
||||
|
||||
def test_schema_versions_types_and_unknown_fields(self):
|
||||
for payload in [{'schema': 2, 'properties': {}}, {'schema': True, 'properties': {}},
|
||||
{'schema': 1, 'properties': {}, 'extra': 1}, {'schema': 1, 'properties': []}]:
|
||||
source = self.source('bad.json', json.dumps(payload).encode())
|
||||
with self.subTest(payload=payload), self.assertRaises(ValueError): platform.load(source)
|
||||
source = self.source('huge.json', b' ' * (platform.MAX_SIZE + 1))
|
||||
with self.assertRaises(ValueError): platform.load(source)
|
||||
|
||||
def test_legacy_migration_never_executes_or_rewrites_source(self):
|
||||
marker = self.root / 'executed'
|
||||
raw = ('SMBIOS_MODEL="ExampleModel1,1"\nSMBIOS_SERIAL_NUMBER="SUPPLIED"\n'
|
||||
f'SMBIOS_CPU_SPEED="$(touch {marker})"\n').encode()
|
||||
source = self.source('darling-smbios.conf', raw)
|
||||
properties = platform.load(source)
|
||||
self.assertTrue(platform.save(self.prefix, properties))
|
||||
self.assertEqual(source.read_bytes(), raw)
|
||||
self.assertFalse(marker.exists())
|
||||
self.assertNotIn('SMBIOS_CPU_SPEED', properties)
|
||||
|
||||
def test_stable_write_backup_permissions_and_roundtrip(self):
|
||||
first = {'IOPlatformSerialNumber': 'FIRST', 'IOPlatformUUID': UUID}
|
||||
self.assertTrue(platform.save(self.prefix, first))
|
||||
path = platform.destination(self.prefix)
|
||||
original = path.read_bytes()
|
||||
self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o600)
|
||||
self.assertFalse(platform.save(self.prefix, platform.load(path)))
|
||||
self.assertEqual(path.read_bytes(), original)
|
||||
self.assertTrue(platform.save(self.prefix, {'IOPlatformSerialNumber': 'SECOND'}))
|
||||
self.assertEqual(path.with_name(path.name+'.before-platform-change').read_bytes(), original)
|
||||
self.assertEqual(platform.load(path), {'IOPlatformSerialNumber': 'SECOND'})
|
||||
|
||||
def test_prefix_isolation(self):
|
||||
other = self.root / 'other'
|
||||
platform.save(self.prefix, {'IOPlatformSerialNumber': 'FIRST'})
|
||||
platform.save(other, {'IOPlatformSerialNumber': 'SECOND'})
|
||||
self.assertEqual(platform.load(platform.destination(self.prefix))['IOPlatformSerialNumber'], 'FIRST')
|
||||
self.assertEqual(platform.load(platform.destination(other))['IOPlatformSerialNumber'], 'SECOND')
|
||||
|
||||
def test_no_implicit_migration_without_an_override_file(self):
|
||||
env = dict(os.environ, DPREFIX=str(self.prefix))
|
||||
result = subprocess.run([sys.executable, str(SCRIPT)], env=env, capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(json.loads(result.stdout)['properties'], {})
|
||||
self.assertFalse(self.prefix.exists())
|
||||
|
||||
def test_preview_and_uuid_replacement_preserve_other_values(self):
|
||||
platform.save(self.prefix, {'IOPlatformSerialNumber': 'FIRST', 'IOPlatformUUID': UUID})
|
||||
replacement = '11112233-4455-4677-8899-AABBCCDDEEFF'
|
||||
command = [sys.executable, str(SCRIPT), '--prefix', str(self.prefix), '--set', 'IOPlatformUUID='+replacement]
|
||||
path = platform.destination(self.prefix)
|
||||
before = path.read_bytes()
|
||||
preview = subprocess.run(command, capture_output=True, text=True)
|
||||
self.assertEqual(preview.returncode, 0, preview.stderr)
|
||||
self.assertEqual(path.read_bytes(), before)
|
||||
written = subprocess.run([*command, '--write'], capture_output=True, text=True)
|
||||
self.assertEqual(written.returncode, 0, written.stderr)
|
||||
result = platform.load(path)
|
||||
self.assertEqual(result['IOPlatformSerialNumber'], 'FIRST')
|
||||
self.assertEqual(result['IOPlatformUUID'], replacement)
|
||||
self.assertEqual(result['system-id'], uuid.UUID(replacement).bytes_le)
|
||||
|
||||
def test_symlink_escape_is_rejected(self):
|
||||
outside = self.root / 'outside'
|
||||
outside.mkdir()
|
||||
(self.prefix / 'private').mkdir(parents=True)
|
||||
(self.prefix / 'private/etc').symlink_to(outside, target_is_directory=True)
|
||||
with self.assertRaises(ValueError): platform.save(self.prefix, {})
|
||||
self.assertEqual(list(outside.iterdir()), [])
|
||||
|
||||
def test_live_prefix_and_unknown_existing_schema_are_not_overwritten(self):
|
||||
self.prefix.mkdir()
|
||||
marker = self.prefix / '.init.pid'
|
||||
marker.write_text(str(os.getpid()))
|
||||
with self.assertRaises(ValueError): platform.save(self.prefix, {})
|
||||
marker.unlink()
|
||||
path = platform.destination(self.prefix)
|
||||
path.parent.mkdir(parents=True)
|
||||
bad = plistlib.dumps({'schema': 999, 'properties': {}})
|
||||
path.write_bytes(bad)
|
||||
with self.assertRaises(ValueError): platform.save(self.prefix, {})
|
||||
self.assertEqual(path.read_bytes(), bad)
|
||||
|
||||
def test_duplicate_json_and_invalid_live_marker_are_rejected(self):
|
||||
duplicate = self.source('duplicate.json', b'{"schema":1,"schema":2,"properties":{}}')
|
||||
with self.assertRaises(ValueError): platform.load(duplicate)
|
||||
self.prefix.mkdir()
|
||||
(self.prefix / '.init.pid').write_text('0')
|
||||
with self.assertRaises(ValueError): platform.save(self.prefix, {})
|
||||
|
||||
def test_malformed_xml_reports_an_error_without_a_traceback(self):
|
||||
source = self.source('bad.plist', b'<?xml version="1.0"?><broken>')
|
||||
result = subprocess.run([sys.executable, str(SCRIPT), '--prefix', str(self.prefix), '--import', str(source)], capture_output=True, text=True)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertNotIn('Traceback', result.stderr)
|
||||
self.assertFalse(self.prefix.exists())
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build an isolated iokitd candidate and a real-object registry/identity probe."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--source', type=Path, default=root / 'build/darling-src/src/external/iokitd',
|
||||
help='Prepared iokitd source directory')
|
||||
args = parser.parse_args()
|
||||
folder = root / 'build/darling-src/build/src/external/iokitd'
|
||||
flags = {}
|
||||
resource = subprocess.check_output(['clang', '-print-resource-dir'], text=True).strip()
|
||||
for line in (folder / 'CMakeFiles/iokitd.dir/flags.make').read_text().splitlines():
|
||||
if line.startswith(('C_', 'CXX_')) and ' = ' in line:
|
||||
key, value = line.split(' = ', 1)
|
||||
flags[key] = [re.sub(r'/usr/lib/clang/\d+/include', resource + '/include', value) for value in shlex.split(value)]
|
||||
link = shlex.split((folder / 'CMakeFiles/iokitd.dir/link.txt').read_text())
|
||||
|
||||
|
||||
def compile(source, output, kind):
|
||||
subprocess.run(['clang++' if kind == 'CXX' else 'clang', *flags[kind + '_DEFINES'],
|
||||
'-I' + str(args.source / 'src'), *flags[kind + '_INCLUDES'], *flags[kind + '_FLAGS'],
|
||||
'-O2', '-c', str(source), '-o', str(output)], check=True)
|
||||
|
||||
|
||||
for argument in link[:]:
|
||||
if argument.startswith('CMakeFiles/iokitd.dir/src/') and argument.endswith('.o'):
|
||||
relative = argument.split('iokitd.dir/', 1)[1][:-2]
|
||||
source = args.source / relative
|
||||
output = root / ('build/registry-candidate-' + source.name + '.o')
|
||||
compile(source, output, 'CXX' if source.suffix in ('.mm', '.cpp') else 'C')
|
||||
link[link.index(argument)] = str(output)
|
||||
link[link.index('-o') + 1] = str(root / 'build/riot-iokitd-candidate')
|
||||
subprocess.run(link, cwd=folder, check=True)
|
||||
probe = root / 'build/registry-compatibility-probe.o'
|
||||
compile(root / 'tools/riot-registry-compatibility-check.mm', probe, 'CXX')
|
||||
link[link.index(str(root / 'build/registry-candidate-main.mm.o'))] = str(probe)
|
||||
user = root / 'build/registry-compatibility-client.o'
|
||||
compile(folder / 'iokitmigUser.c', user, 'C')
|
||||
link.append(str(user))
|
||||
link[link.index('-o') + 1] = str(root / 'build/riot-registry-compatibility-check')
|
||||
subprocess.run(link, cwd=folder, check=True)
|
||||
print('Built isolated iokitd candidate and registry compatibility probe')
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preview or explicitly save per-prefix IOKit platform overrides; defaults stay unchanged."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import shlex
|
||||
import tempfile
|
||||
import uuid
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
||||
MAX_SIZE = 65536
|
||||
STRINGS = {'IOPlatformSerialNumber': 64, 'manufacturer': 128, 'model': 128,
|
||||
'product-name': 128, 'board-id': 128, 'mlb-serial-number': 64}
|
||||
BYTES = {'ROM': 6, 'system-id': 16}
|
||||
ALIASES = {
|
||||
'SystemProductName': 'model', 'Type': 'model', 'SMBIOS_MODEL': 'model',
|
||||
'SMBIOS_PRODUCT_NAME': 'product-name', 'SMBIOS_MANUFACTURER': 'manufacturer',
|
||||
'SMBIOS_BOARD_ID': 'board-id', 'SystemSerialNumber': 'IOPlatformSerialNumber',
|
||||
'Serial': 'IOPlatformSerialNumber', 'SMBIOS_SERIAL_NUMBER': 'IOPlatformSerialNumber',
|
||||
'SystemUUID': 'IOPlatformUUID', 'SmUUID': 'IOPlatformUUID', 'SMBIOS_PLATFORM_UUID': 'IOPlatformUUID',
|
||||
'MLB': 'mlb-serial-number', 'Board Serial': 'mlb-serial-number', 'SMBIOS_MLB': 'mlb-serial-number',
|
||||
'SMBIOS_ROM': 'ROM', 'SMBIOS_SYSTEM_ID': 'system-id',
|
||||
}
|
||||
|
||||
|
||||
def validate(properties):
|
||||
if not isinstance(properties, dict) or len(properties) > 9:
|
||||
raise ValueError('properties must be a dictionary of supported identity fields')
|
||||
result = {}
|
||||
for key, value in properties.items():
|
||||
if key in STRINGS:
|
||||
if not isinstance(value, str) or not 1 <= len(value) <= STRINGS[key] or any(not 32 <= ord(c) <= 126 for c in value):
|
||||
raise ValueError('Invalid printable string for ' + key)
|
||||
elif key == 'IOPlatformUUID':
|
||||
if not isinstance(value, str):
|
||||
raise ValueError('IOPlatformUUID must be a UUID string')
|
||||
parsed = uuid.UUID(value)
|
||||
if not parsed.int:
|
||||
raise ValueError('IOPlatformUUID must not be zero')
|
||||
value = str(parsed).upper()
|
||||
elif key in BYTES:
|
||||
if isinstance(value, str):
|
||||
value = bytes.fromhex(value.replace(':', '').replace('-', ''))
|
||||
if not isinstance(value, bytes) or len(value) != BYTES[key]:
|
||||
raise ValueError(key + ' has an invalid byte length')
|
||||
else:
|
||||
raise ValueError('Unsupported identity field: ' + str(key))
|
||||
result[key] = value
|
||||
if 'model' in result:
|
||||
result.setdefault('product-name', result['model'])
|
||||
if 'IOPlatformUUID' in result:
|
||||
system_id = uuid.UUID(result['IOPlatformUUID']).bytes_le
|
||||
if 'system-id' in result and result['system-id'] != system_id:
|
||||
raise ValueError('system-id does not match IOPlatformUUID')
|
||||
result['system-id'] = system_id
|
||||
return result
|
||||
|
||||
|
||||
def load(source):
|
||||
with Path(source).open("rb") as stream:
|
||||
raw = stream.read(MAX_SIZE + 1)
|
||||
if len(raw) > MAX_SIZE:
|
||||
raise ValueError('Identity input exceeds 64 KiB')
|
||||
if str(source).endswith('.conf'):
|
||||
payload = {}
|
||||
for line in raw.decode().splitlines():
|
||||
if not line.strip() or line.lstrip().startswith('#'):
|
||||
continue
|
||||
key, sep, value = line.partition('=')
|
||||
if not sep:
|
||||
raise ValueError('Malformed legacy identity line')
|
||||
values = shlex.split(value, comments=True)
|
||||
if len(values) != 1 or key.strip() in payload:
|
||||
raise ValueError('Malformed or duplicate legacy identity field')
|
||||
payload[key.strip()] = values[0]
|
||||
else:
|
||||
def unique_keys(pairs):
|
||||
result = {}
|
||||
for key, value in pairs:
|
||||
if key in result: raise ValueError('Duplicate JSON identity field: ' + key)
|
||||
result[key] = value
|
||||
return result
|
||||
if raw.lstrip()[:1] in (b'{', b'['):
|
||||
payload = json.loads(raw, object_pairs_hook=unique_keys)
|
||||
else:
|
||||
payload = plistlib.loads(raw)
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError('Identity input must be a dictionary')
|
||||
if 'schema' in payload:
|
||||
if set(payload) != {'schema', 'properties'} or type(payload['schema']) is not int or payload['schema'] != 1:
|
||||
raise ValueError('Unsupported platform identity schema')
|
||||
return validate(payload['properties'])
|
||||
if 'PlatformInfo' in payload:
|
||||
payload = payload['PlatformInfo'].get('Generic') if isinstance(payload['PlatformInfo'], dict) else None
|
||||
elif 'Generic' in payload:
|
||||
payload = payload['Generic']
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError('Missing OpenCore PlatformInfo/Generic dictionary')
|
||||
result = {}
|
||||
for key, value in payload.items():
|
||||
name = ALIASES.get(key, key)
|
||||
if name not in {*STRINGS, *BYTES, 'IOPlatformUUID'}:
|
||||
continue # OpenCore includes unrelated boot, firmware and CPU settings.
|
||||
if name in result and result[name] != value:
|
||||
raise ValueError('Conflicting imports for ' + name)
|
||||
result[name] = value
|
||||
if not result:
|
||||
raise ValueError('No supported identity fields found')
|
||||
return validate(result)
|
||||
|
||||
|
||||
def destination(prefix):
|
||||
prefix = Path(prefix).expanduser()
|
||||
if not prefix.is_absolute():
|
||||
raise ValueError('Prefix must be absolute')
|
||||
path = prefix / 'private/etc/darling-platform.plist'
|
||||
if path.is_symlink() or not path.resolve().is_relative_to(prefix.resolve()):
|
||||
raise ValueError('Identity destination must remain inside the selected prefix')
|
||||
return path
|
||||
|
||||
|
||||
def save(prefix, properties):
|
||||
path = destination(prefix)
|
||||
prefix = Path(prefix)
|
||||
# A host write behind Darling's mounted overlay may not be visible. Never
|
||||
# stop a user's session implicitly or write a second copy behind its back.
|
||||
marker = prefix / '.init.pid'
|
||||
if marker.exists():
|
||||
try:
|
||||
text = marker.read_text().strip()
|
||||
if not text.isdecimal() or int(text) <= 1:
|
||||
raise ValueError('Invalid .init.pid; verify the selected prefix is stopped before writing')
|
||||
pid = int(text)
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
else:
|
||||
raise ValueError('Shut down the selected Darling prefix before writing platform identity')
|
||||
encoded = plistlib.dumps({'schema': 1, 'properties': validate(properties)}, sort_keys=True)
|
||||
if path.exists() and path.read_bytes() == encoded:
|
||||
return False
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Recheck after creating parents, before opening a temporary file.
|
||||
path = destination(prefix)
|
||||
if path.exists():
|
||||
load(path) # refuse to silently overwrite an unknown schema
|
||||
backup = path.with_name(path.name + '.before-platform-change')
|
||||
if not backup.exists():
|
||||
fd = os.open(backup, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
||||
with os.fdopen(fd, 'wb') as stream:
|
||||
stream.write(path.read_bytes())
|
||||
fd, temporary = tempfile.mkstemp(prefix='.darling-platform-', dir=path.parent)
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as stream:
|
||||
stream.write(encoded)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(temporary, path)
|
||||
finally:
|
||||
Path(temporary).unlink(missing_ok=True)
|
||||
return True
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--prefix', type=Path)
|
||||
parser.add_argument('--import', dest='source', type=Path, help='OpenCore plist, GenSMBIOS JSON, legacy .conf, or schema-1 plist/JSON')
|
||||
parser.add_argument('--set', action='append', default=[], metavar='KEY=VALUE')
|
||||
parser.add_argument('--write', action='store_true', help='Write validated overrides to a stopped prefix (otherwise preview only)')
|
||||
args = parser.parse_args(argv)
|
||||
config = Path(__file__).resolve().parents[1] / 'build/config.json'
|
||||
saved = json.loads(config.read_text()) if config.exists() else {}
|
||||
prefix = args.prefix or Path(os.environ.get('DPREFIX') or saved.get('prefix', str(Path.home() / '.darling')))
|
||||
try:
|
||||
path = destination(prefix)
|
||||
properties = load(path) if path.exists() else {}
|
||||
if args.source:
|
||||
incoming = load(args.source)
|
||||
# Replacing a UUID also replaces its previously derived byte form.
|
||||
if 'IOPlatformUUID' in incoming:
|
||||
properties.pop('system-id', None)
|
||||
properties.update(incoming)
|
||||
for assignment in args.set:
|
||||
key, sep, value = assignment.partition('=')
|
||||
if not sep:
|
||||
raise ValueError('--set requires KEY=VALUE')
|
||||
if key == 'IOPlatformUUID': properties.pop('system-id', None)
|
||||
if key == 'model': properties.pop('product-name', None)
|
||||
properties[key] = value
|
||||
properties = validate(properties)
|
||||
if args.write:
|
||||
changed = save(prefix, properties)
|
||||
print('Platform identity ' + ('saved' if changed else 'unchanged') + '; restart iokitd to apply.')
|
||||
else:
|
||||
print(json.dumps({'schema': 1, 'properties': properties}, default=lambda value: value.hex().upper(), indent=2))
|
||||
except (OSError, ValueError, TypeError, plistlib.InvalidFileException, ExpatError) as error:
|
||||
parser.exit(1, str(error) + '\n')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,194 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "IORegistryEntry.h"
|
||||
#include "PlatformIdentity.h"
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <IOKit/IOKitKeys.h>
|
||||
#include <IOKit/IOCFUnserialize.h>
|
||||
#include <mach/mach.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <dispatch/dispatch.h>
|
||||
extern "C" {
|
||||
#include "iokitmigServer.h"
|
||||
#include "iokitmig.h"
|
||||
}
|
||||
|
||||
// This probe invokes the real daemon objects and MIG handlers on the main
|
||||
// thread, without registering a daemon or changing the running iokitd.
|
||||
mach_port_t g_masterPort, g_deathPort, g_powerManagementPort;
|
||||
struct Fixture : IORegistryEntry {
|
||||
NSDictionary* getProperties() override {
|
||||
return @{@"text": @"hello", @"utf8": @"é", @"small": [NSNumber numberWithUnsignedInt:0x12345678],
|
||||
@"negative": [NSNumber numberWithInt:-3], @"wide": [NSNumber numberWithUnsignedLongLong:0x123456789abcdef0ULL],
|
||||
@"flag": @YES, @"float": @1.25, @"array": @[], @"large": [NSMutableData dataWithLength:5000]};
|
||||
}
|
||||
};
|
||||
static void checkProperties(Fixture& fixture) {
|
||||
io_struct_inband_t buffer; mach_msg_type_number_t length;
|
||||
io_name_t key = "text";
|
||||
memset(buffer, 0x7e, sizeof(buffer)); length = 2;
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnIPCError);
|
||||
assert(length == 2 && buffer[0] == 0x7e);
|
||||
length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
assert(length == 6 && !memcmp(buffer, "hello", 6));
|
||||
strcpy(key, "small"); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
uint64_t small = 0; memcpy(&small, buffer, length); assert(length == CFNumberGetByteSize((CFNumberRef)fixture.getProperties()[@"small"]) && small == 0x12345678);
|
||||
strcpy(key, "negative"); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
int64_t negative = -1; memcpy(&negative, buffer, length); assert(length == CFNumberGetByteSize((CFNumberRef)fixture.getProperties()[@"negative"]) && negative == -3);
|
||||
strcpy(key, "wide"); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
uint64_t wide; memcpy(&wide, buffer, sizeof(wide)); assert(length == 8 && wide == 0x123456789abcdef0ULL);
|
||||
strcpy(key, "flag"); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
assert(length == 4 && !memcmp(buffer, "Yes", 4));
|
||||
strcpy(key, "utf8"); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnSuccess);
|
||||
assert(length == 3 && !memcmp(buffer, "\xc3\xa9", 3));
|
||||
strcpy(key, "large"); length = UINT32_MAX; memset(buffer, 0x7e, sizeof(buffer));
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnIPCError);
|
||||
assert(length == sizeof(buffer) && buffer[0] == 0x7e && buffer[sizeof(buffer)-1] == 0x7e);
|
||||
for (const char* name : {"float", "array", "missing"}) {
|
||||
strcpy(key, name); length = sizeof(buffer);
|
||||
auto expected = !strcmp(name,"missing") ? kIOReturnNoResources : kIOReturnBadArgument;
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == expected && length == 0);
|
||||
}
|
||||
memset(key, 'x', sizeof(key)); length = sizeof(buffer);
|
||||
assert(is_io_registry_entry_get_property_bytes(fixture.port(), key, buffer, &length) == kIOReturnBadArgument && length == 0);
|
||||
}
|
||||
static void checkIdentity() {
|
||||
NSString* uuid = @"00112233-4455-4677-8899-AABBCCDDEEFF";
|
||||
auto* valid = DarlingPlatformIdentity::properties(@{@"schema": @1, @"properties": @{@"model": @"ExampleModel1,1", @"IOPlatformUUID": uuid, @"IOPlatformSerialNumber": @"SUPPLIED"}});
|
||||
assert(valid && [valid[@"IOPlatformUUID"] isEqual:uuid]);
|
||||
assert([valid[@"model"] isKindOfClass:[NSData class]] && [valid[@"model"] isEqual:valid[@"product-name"]]);
|
||||
const unsigned char bytes[] = {0x33,0x22,0x11,0,0x55,0x44,0x77,0x46,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff};
|
||||
assert([valid[@"system-id"] isEqual:[NSData dataWithBytes:bytes length:16]]);
|
||||
for (NSDictionary* invalid in @[
|
||||
@{@"schema": @2, @"properties": @{}}, @{@"schema": @YES, @"properties": @{}},
|
||||
@{@"schema": @1, @"properties": @{@"unknown": @"value"}},
|
||||
@{@"schema": @1, @"properties": @{@"ROM": [NSData data]}},
|
||||
@{@"schema": @1, @"properties": @{@"IOPlatformUUID": @"00000000-0000-0000-0000-000000000000"}},
|
||||
@{@"schema": @1, @"properties": @{@"IOPlatformUUID": uuid, @"system-id": [NSMutableData dataWithLength:16]}},
|
||||
@{@"schema": @1, @"properties": @{@"model": @"bad\nmodel"}}
|
||||
]) assert(!DarlingPlatformIdentity::properties(invalid));
|
||||
assert(!DarlingPlatformIdentity::load("/private/etc/does-not-exist-platform-test.plist"));
|
||||
// Exercise the same plist reader used by startup, using only a disposable file.
|
||||
char filename[] = "/tmp/darling-platform-probe.XXXXXX";
|
||||
int descriptor = mkstemp(filename); assert(descriptor >= 0);
|
||||
NSDictionary* config = @{@"schema": @1, @"properties": @{@"IOPlatformSerialNumber": @"FILE-FIXTURE", @"ROM": [NSData dataWithBytes:"abcdef" length:6]}};
|
||||
NSData* encoded = [NSPropertyListSerialization dataWithPropertyList:config format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
|
||||
assert(encoded);
|
||||
FILE* output = fdopen(descriptor,"wb"); assert(output);
|
||||
assert(fwrite([encoded bytes],1,[encoded length],output) == [encoded length]); fclose(output);
|
||||
NSDictionary* loaded = DarlingPlatformIdentity::load(filename);
|
||||
assert(loaded && [loaded[@"IOPlatformSerialNumber"] isEqual:@"FILE-FIXTURE"] && [loaded[@"ROM"] length] == 6);
|
||||
unlink(filename);
|
||||
}
|
||||
int main() {
|
||||
@autoreleasepool {
|
||||
Fixture fixture;
|
||||
std::string temporaryPlane = "OwnedPlane";
|
||||
fixture.registerInPlane(temporaryPlane.c_str(), "Child", IORegistryEntry::root());
|
||||
temporaryPlane.assign(1000, 'x');
|
||||
assert(fixture.getPath("OwnedPlane") == "OwnedPlane:/Child");
|
||||
assert(IORegistryEntry::fromPath("OwnedPlane:/Child") == &fixture);
|
||||
assert(IORegistryEntry::fromPath("IOService:/") == IORegistryEntry::root());
|
||||
assert(!IORegistryEntry::fromPath("MissingPlane:/"));
|
||||
assert(!IORegistryEntry::fromPath("OwnedPlane:/child"));
|
||||
assert(!IORegistryEntry::fromPath("OwnedPlane:/../Child"));
|
||||
io_name_t plane = "OwnedPlane"; boolean_t inPlane = TRUE;
|
||||
assert(is_io_registry_entry_in_plane(fixture.port(), plane, &inPlane) == kIOReturnSuccess && inPlane);
|
||||
strcpy(plane, "Other");
|
||||
assert(is_io_registry_entry_in_plane(fixture.port(), plane, &inPlane) == kIOReturnSuccess && !inPlane);
|
||||
std::vector<std::unique_ptr<IORegistryEntry>> entries;
|
||||
IORegistryEntry* parent = &fixture;
|
||||
for (unsigned i=0; i<40; ++i) {
|
||||
auto entry = std::make_unique<IORegistryEntry>();
|
||||
entry->registerInPlane("OwnedPlane", ("Entry"+std::to_string(i)+std::string(110,'x')).c_str(), parent);
|
||||
parent = entry.get(); entries.push_back(std::move(entry));
|
||||
}
|
||||
auto longPath = parent->getPath("OwnedPlane"); assert(longPath.size() > sizeof(io_string_inband_t));
|
||||
assert(IORegistryEntry::fromPath(longPath) == parent);
|
||||
// An attempted cycle must not alter an existing root path.
|
||||
fixture.registerInPlane("OwnedPlane", "cycle", parent);
|
||||
assert(fixture.getPath("OwnedPlane") == "OwnedPlane:/Child");
|
||||
strcpy(plane, "OwnedPlane");
|
||||
io_string_t fixed; memset(fixed,'x',sizeof(fixed));
|
||||
assert(is_io_registry_entry_get_path(parent->port(), plane, fixed) == kIOReturnNoSpace && fixed[0] == 0);
|
||||
io_string_inband_t inlinePath = ""; io_buf_ptr_t out = nullptr; mach_msg_type_number_t outCount = 0;
|
||||
assert(is_io_registry_entry_get_path_ool(parent->port(), plane, inlinePath, &out, &outCount) == kIOReturnSuccess);
|
||||
assert(!inlinePath[0] && std::string(out,outCount) == longPath && outCount == longPath.size());
|
||||
for (unsigned i=0; i<1000; ++i) {
|
||||
vm_address_t allocation = 0;
|
||||
assert(vm_allocate(mach_task_self(), &allocation, longPath.size()+1, TRUE) == KERN_SUCCESS);
|
||||
memcpy((void*)allocation, longPath.c_str(), longPath.size()+1);
|
||||
kern_return_t result; mach_port_t found;
|
||||
assert(is_io_registry_entry_from_path_ool(0, inlinePath, (io_buf_ptr_t)allocation, longPath.size()+1, &result, &found) == kIOReturnSuccess);
|
||||
assert(result == kIOReturnSuccess && found == parent->port());
|
||||
}
|
||||
// Malformed OOL input must fail without publishing a port.
|
||||
vm_address_t bad = 0;
|
||||
assert(vm_allocate(mach_task_self(), &bad, 4, TRUE) == KERN_SUCCESS);
|
||||
memset((void*)bad, 'x', 4);
|
||||
kern_return_t badResult; mach_port_t badEntry = 123;
|
||||
assert(is_io_registry_entry_from_path_ool(0, inlinePath, (io_buf_ptr_t)bad, 4, &badResult, &badEntry) == kIOReturnSuccess);
|
||||
assert(badResult == kIOReturnBadArgument && badEntry == MACH_PORT_NULL);
|
||||
auto detachedParent = std::make_unique<IORegistryEntry>();
|
||||
auto detachedChild = std::make_unique<IORegistryEntry>();
|
||||
detachedParent->registerInPlane("OwnedPlane", "Parent", &fixture);
|
||||
detachedChild->registerInPlane("OwnedPlane", "Child", detachedParent.get());
|
||||
assert(detachedChild->inPlane("OwnedPlane"));
|
||||
detachedParent.reset();
|
||||
assert(!detachedChild->inPlane("OwnedPlane") && detachedChild->getPath("OwnedPlane") == "?unsup_plane?");
|
||||
auto deleted = std::make_unique<IORegistryEntry>();
|
||||
deleted->registerInPlane("OwnedPlane", "Deleted", &fixture);
|
||||
assert(IORegistryEntry::fromPath("OwnedPlane:/Child/Deleted") == deleted.get());
|
||||
deleted.reset();
|
||||
assert(!IORegistryEntry::fromPath("OwnedPlane:/Child/Deleted"));
|
||||
checkProperties(fixture); checkIdentity();
|
||||
struct Serializable : IORegistryEntry {
|
||||
NSDictionary* getProperties() override { return @{@"text": @"value", @"data": [NSData dataWithBytes:"abc" length:3], @"number": @123, @"array": @[@YES, @"x"]}; }
|
||||
} serializable;
|
||||
io_buf_ptr_t serialized = nullptr; mach_msg_type_number_t serializedCount = 0;
|
||||
assert(is_io_registry_entry_get_properties_bin(serializable.port(), &serialized, &serializedCount) == kIOReturnSuccess);
|
||||
CFStringRef error = nullptr;
|
||||
CFTypeRef decoded = IOCFUnserializeWithSize(serialized, serializedCount, kCFAllocatorDefault, 0, &error);
|
||||
assert(decoded && !error && [(NSDictionary*)decoded isEqual:serializable.getProperties()]);
|
||||
CFRelease(decoded);
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)serialized, serializedCount);
|
||||
std::thread([&fixture, parent, &serializable, longPath] {
|
||||
@autoreleasepool {
|
||||
io_name_t plane = "OwnedPlane";
|
||||
for (unsigned i=0; i<100; ++i) {
|
||||
io_string_inband_t path = "";
|
||||
io_buf_ptr_t bytes = nullptr; mach_msg_type_number_t length = 0;
|
||||
assert(io_registry_entry_get_path_ool(parent->port(), plane, path, &bytes, &length) == kIOReturnSuccess);
|
||||
assert(bytes && std::string(bytes,length) == longPath);
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)bytes, length);
|
||||
mach_port_t found = 0; kern_return_t result;
|
||||
assert(io_registry_entry_from_path_ool(fixture.port(), path, const_cast<char*>(longPath.c_str()), longPath.size()+1, &result, &found) == kIOReturnSuccess);
|
||||
assert(result == kIOReturnSuccess && found == parent->port());
|
||||
mach_port_deallocate(mach_task_self(), found);
|
||||
}
|
||||
io_name_t property = "flag"; io_struct_inband_t raw; mach_msg_type_number_t count = sizeof(raw);
|
||||
assert(io_registry_entry_get_property_bytes(fixture.port(), property, raw, &count) == kIOReturnSuccess && count == 4 && !memcmp(raw,"Yes",4));
|
||||
io_buf_ptr_t bytes = nullptr; count = 0;
|
||||
assert(io_registry_entry_get_properties_bin(serializable.port(), &bytes, &count) == kIOReturnSuccess);
|
||||
CFStringRef error = nullptr;
|
||||
CFTypeRef decoded = IOCFUnserializeWithSize(bytes, count, kCFAllocatorDefault, 0, &error);
|
||||
assert(decoded && !error && [(NSDictionary*)decoded isEqual:serializable.getProperties()]);
|
||||
CFRelease(decoded); vm_deallocate(mach_task_self(), (vm_address_t)bytes, count);
|
||||
puts("REGISTRY_COMPAT paths=1 planes=1 graph_lifetime=1 cycles=1 ool_roundtrips=1000 ipc_roundtrips=100 property_bounds=1 numeric_widths=1 serialization=1 identity_schema=1 pass=1");
|
||||
fflush(stdout);
|
||||
_Exit(0);
|
||||
}
|
||||
}).detach();
|
||||
dispatch_main();
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@
|
||||
"revision": "5549ac4cd2db923c256f016a1381b3bfb716730a",
|
||||
"repository": "https://github.com/darlinghq/darling-iokitd.git",
|
||||
"patch": "patches/iokitd.patch",
|
||||
"modified_files": 16,
|
||||
"modified_files": 17,
|
||||
"new_files": [
|
||||
"src/IOInterestNotification.h",
|
||||
"src/IOInterestNotification.mm",
|
||||
@@ -138,7 +138,8 @@
|
||||
"src/LogindSleep.h",
|
||||
"src/PowerSources.mm",
|
||||
"src/HostPowerInhibit.c",
|
||||
"src/HostPowerInhibit.h"
|
||||
"src/HostPowerInhibit.h",
|
||||
"src/PlatformIdentity.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user