fix: USTAR prefix-field parsing and directory-aware chdir

Two ramdisk fixes exposed by shipping the C++ header tree:

Paths longer than 100 characters (libstdc++'s pb_ds detail headers)
are split by USTAR across the name field and the 155-byte prefix
field at offset 345. The parser only read the name field, so the
tails of 7 deep header paths appeared as bogus root-level entries
while the real paths were missing. The parser now joins
prefix + '/' + name, and MaxNameLen grows to 260 to hold the full
combined path.

SYS_CHDIR validated non-root targets by opening them as files, which
only ever worked because directories used to be openable. It now uses
the ReadDir probe, which fails for nonexistent paths and regular
files on all backends (ext2/fat32 already validate the inode type).

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-17 10:18:58 +02:00
co-authored by Claude Fable 5
parent aa3b92d834
commit 78ce551688
4 changed files with 25 additions and 12 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 21 #define MONTAUK_BUILD_NUMBER 22
+5 -5
View File
@@ -194,13 +194,13 @@ namespace montauk::abi {
} }
} }
if (isDriveRoot) { // ReadDir doubles as the directory-existence probe: it fails
// for nonexistent paths and for regular files (directories are
// not openable as files anymore, so the old open-based check
// would reject every valid target).
{
const char* entries[1]; const char* entries[1];
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1; if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
} else {
Fs::Vfs::BackendFile file = {-1, -1, 0};
if (Fs::Vfs::OpenBackendFile(resolved, file) < 0) return -1;
Fs::Vfs::CloseBackendFile(file);
} }
int i = 0; int i = 0;
+17 -5
View File
@@ -107,8 +107,13 @@ namespace Fs::Ramdisk {
break; break;
} }
// File name at offset 0 (100 bytes) // File name at offset 0 (100 bytes), long-path directory
// part in the USTAR prefix field at offset 345 (155 bytes).
// Paths over 100 chars (deep C++ header trees) are split
// across the two; joining them is required or the tail of
// the name lands as a bogus root-level entry.
const char* name = (const char*)ptr; const char* name = (const char*)ptr;
const char* namePrefix = (const char*)(ptr + 345);
// File size at offset 124 (12 bytes, octal ASCII) // File size at offset 124 (12 bytes, octal ASCII)
uint64_t size = OctalToUint((const char*)(ptr + 124), 12); uint64_t size = OctalToUint((const char*)(ptr + 124), 12);
// Type flag at offset 156 // Type flag at offset 156
@@ -116,11 +121,18 @@ namespace Fs::Ramdisk {
FileEntry& entry = fileTable[fileCount]; FileEntry& entry = fileTable[fileCount];
// Copy name // Copy prefix + '/' + name (prefix may be empty)
int nameLen = 0; int nameLen = 0;
while (nameLen < MaxNameLen - 1 && name[nameLen] != '\0') { if (namePrefix[0] != '\0') {
entry.name[nameLen] = name[nameLen]; for (int j = 0; j < 155 && namePrefix[j] != '\0'
nameLen++; && nameLen < MaxNameLen - 2; j++) {
entry.name[nameLen++] = namePrefix[j];
}
entry.name[nameLen++] = '/';
}
for (int j = 0; j < 100 && name[j] != '\0'
&& nameLen < MaxNameLen - 1; j++) {
entry.name[nameLen++] = name[j];
} }
entry.name[nameLen] = '\0'; entry.name[nameLen] = '\0';
+2 -1
View File
@@ -13,7 +13,8 @@ namespace Fs::Ramdisk {
// Static file table. The base image ships ~650 files since the // Static file table. The base image ships ~650 files since the
// 0:/usr devkit; keep generous headroom for runtime file creation. // 0:/usr devkit; keep generous headroom for runtime file creation.
static constexpr int MaxFiles = 2048; static constexpr int MaxFiles = 2048;
static constexpr int MaxNameLen = 100; // USTAR: 155-byte prefix field + '/' + 100-byte name field.
static constexpr int MaxNameLen = 260;
struct FileEntry { struct FileEntry {
char name[MaxNameLen]; char name[MaxNameLen];