boot: survive hand-written sticks — case-fold the /system walk, skip host litter
The loader ENUMERATES /system now, so it sees whatever the stick's directory entries literally store — and a hand-copied stick differs from our generated image: firmware returns bare 8.3 short entries UPPERCASE (INIT, SYSTEM), and host OSes leave litter next to every file (macOS '._' AppleDouble forks, .fseventsd). Verified in QEMU/OVMF: an uppercase-stored volume booted to a dead kernel-only system before this change and boots fully (display up, logger writing /var/log) after. The walk now lowers ASCII names (the danos tree is canonically lowercase; FAT lookups are case-insensitive by definition), skips any dot-prefixed entry, and treats malformed or unreadable entries as skip-this-file instead of abort-the-whole-walk. Reader.find compares case-insensitively as belt and braces.
This commit is contained in:
+26
-9
@@ -448,17 +448,34 @@ fn walkDirectory(
|
|||||||
const info: *const uefi.protocol.File.Info.File = @ptrCast(@alignCast(&info_buffer));
|
const info: *const uefi.protocol.File.Info.File = @ptrCast(@alignCast(&info_buffer));
|
||||||
const name16 = info.getFileName();
|
const name16 = info.getFileName();
|
||||||
|
|
||||||
// Convert the (ASCII in practice) UTF-16 name; skip "." and "..".
|
// Convert the (ASCII in practice) UTF-16 name. A hostile-shaped entry
|
||||||
|
// (too long, non-ASCII) is SKIPPED, never fatal — one odd file on a
|
||||||
|
// hand-written stick must not cost the whole boot. Names are lowered:
|
||||||
|
// the danos tree is canonically lowercase and FAT lookups are
|
||||||
|
// case-insensitive, but firmware ENUMERATION returns whatever the
|
||||||
|
// directory stores — an 8.3 short entry comes back uppercase ("INIT"),
|
||||||
|
// which would otherwise poison every path comparison downstream.
|
||||||
var name_buffer: [initial_ramdisk.maximum_name]u8 = undefined;
|
var name_buffer: [initial_ramdisk.maximum_name]u8 = undefined;
|
||||||
var name_length: usize = 0;
|
var name_length: usize = 0;
|
||||||
|
var name_ok = true;
|
||||||
while (name16[name_length] != 0) : (name_length += 1) {
|
while (name16[name_length] != 0) : (name_length += 1) {
|
||||||
if (name_length == name_buffer.len) return error.NameTooLong;
|
if (name_length == name_buffer.len) {
|
||||||
const c = name16[name_length];
|
name_ok = false;
|
||||||
if (c > 0x7F) return error.UnsupportedName;
|
break;
|
||||||
name_buffer[name_length] = @intCast(c);
|
|
||||||
}
|
}
|
||||||
|
const c = name16[name_length];
|
||||||
|
if (c > 0x7F) {
|
||||||
|
name_ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
name_buffer[name_length] = std.ascii.toLower(@intCast(c));
|
||||||
|
}
|
||||||
|
if (!name_ok) continue;
|
||||||
const name = name_buffer[0..name_length];
|
const name = name_buffer[0..name_length];
|
||||||
if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
|
// Skip dot entries: "." / ".." and host-OS litter (macOS "._*" AppleDouble
|
||||||
|
// resource forks, ".fseventsd", ".Spotlight-V100") a copied-onto stick
|
||||||
|
// accumulates — none of it is a danos binary.
|
||||||
|
if (name.len == 0 or name[0] == '.') continue;
|
||||||
|
|
||||||
if (info.attribute.directory) {
|
if (info.attribute.directory) {
|
||||||
if (depth == maximum_tree_depth) continue;
|
if (depth == maximum_tree_depth) continue;
|
||||||
@@ -474,12 +491,12 @@ fn walkDirectory(
|
|||||||
if (count.* == maximum_bundled) return error.TooManyBinaries;
|
if (count.* == maximum_bundled) return error.TooManyBinaries;
|
||||||
|
|
||||||
var entry: *Bundled = &list[count.*];
|
var entry: *Bundled = &list[count.*];
|
||||||
const path = try std.fmt.bufPrint(&entry.path, "{s}/{s}", .{ prefix, name });
|
const path = std.fmt.bufPrint(&entry.path, "{s}/{s}", .{ prefix, name }) catch continue; // path too long: skip the file, keep the boot
|
||||||
entry.path_len = path.len;
|
entry.path_len = path.len;
|
||||||
|
|
||||||
const file = try directory.open(name16, .read, .{});
|
const file = directory.open(name16, .read, .{}) catch continue;
|
||||||
defer _ = file.close() catch {};
|
defer _ = file.close() catch {};
|
||||||
entry.data = try readWholeFile(bs, file);
|
entry.data = readWholeFile(bs, file) catch continue; // unreadable/empty: skip
|
||||||
count.* += 1;
|
count.* += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,17 +76,20 @@ pub const Reader = struct {
|
|||||||
|
|
||||||
/// Look a binary up by name: an exact path match wins; otherwise a unique
|
/// Look a binary up by name: an exact path match wins; otherwise a unique
|
||||||
/// basename match ("fat" finds "/system/services/fat") keeps pre-path callers
|
/// basename match ("fat" finds "/system/services/fat") keeps pre-path callers
|
||||||
/// working. The returned Item's name is always the stored full path.
|
/// working. Comparisons are ASCII case-insensitive — the entries come from a
|
||||||
|
/// FAT volume, whose name lookups are case-insensitive by definition (and
|
||||||
|
/// whose short entries store uppercase). The returned Item's name is always
|
||||||
|
/// the stored full path.
|
||||||
pub fn find(self: Reader, name: []const u8) ?Item {
|
pub fn find(self: Reader, name: []const u8) ?Item {
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < self.count) : (i += 1) {
|
while (i < self.count) : (i += 1) {
|
||||||
const item = self.entry(i) orelse continue;
|
const item = self.entry(i) orelse continue;
|
||||||
if (std.mem.eql(u8, item.name, name)) return item;
|
if (std.ascii.eqlIgnoreCase(item.name, name)) return item;
|
||||||
}
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
while (i < self.count) : (i += 1) {
|
while (i < self.count) : (i += 1) {
|
||||||
const item = self.entry(i) orelse continue;
|
const item = self.entry(i) orelse continue;
|
||||||
if (std.mem.eql(u8, basename(item.name), name)) return item;
|
if (std.ascii.eqlIgnoreCase(basename(item.name), name)) return item;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user