fix(kernel): reject getdents on file fds and emit . / .. for empty dirs (#546)

Returning rax=0 for non-directory or empty listings looked like EOF and
let GTA treat the fd as a pointer (fiWriteAsyncDataWorker AV at 0xB1).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MarcelMediaDev
2026-07-23 13:36:28 +01:00
committed by GitHub
parent 8e1e89c024
commit 007bf6fa73
@@ -7335,28 +7335,41 @@ public static partial class KernelMemoryCompatExports
{ {
if (fd < 0 || bufferAddress == 0 || requested < 512) if (fd < 0 || bufferAddress == 0 || requested < 512)
{ {
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
OpenDirectory? directory; OpenDirectory? directory;
bool isOpenFile;
lock (_fdGate) lock (_fdGate)
{ {
_openDirectories.TryGetValue(fd, out directory); _openDirectories.TryGetValue(fd, out directory);
isOpenFile = directory is null && _openFiles.ContainsKey(fd);
} }
if (directory is null) if (directory is null)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; // A regular file fd used with getdents must not look like EOF (rax=0);
// that path has caused GTA's fiWriteAsyncDataWorker to treat the fd
// integer as a pointer and AV at address 0xB1.
var error = isOpenFile
? OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
: OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
LogIoTrace("getdents", $"fd:{fd}", $"result={(isOpenFile ? "not_directory" : "badfd")}");
ctx[CpuRegister.Rax] = unchecked((ulong)(int)error);
return (int)error;
} }
var currentIndex = directory.NextIndex; var currentIndex = directory.NextIndex;
if (basePointerAddress != 0 && !TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex)) if (basePointerAddress != 0 && !TryWriteUInt64Compat(ctx, basePointerAddress, (ulong)currentIndex))
{ {
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (currentIndex >= directory.Entries.Length) if (currentIndex >= directory.Entries.Length)
{ {
LogIoTrace("getdents", directory.Path, $"fd={fd} result=eof entries={directory.Entries.Length}");
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -7387,11 +7400,16 @@ public static partial class KernelMemoryCompatExports
private static string[] EnumerateDirectoryEntries(string hostPath) private static string[] EnumerateDirectoryEntries(string hostPath)
{ {
return Directory.EnumerateFileSystemEntries(hostPath) // Real getdents always yields "." / ".." before other names. An empty
// host dir previously returned EOF on the first call (rax=0), which
// sent GTA's fiWriteAsyncDataWorker down a path that treated the fd as
// a pointer (AV at 0xB1 on /download0/cloudcache/).
var children = Directory.EnumerateFileSystemEntries(hostPath)
.Select(Path.GetFileName) .Select(Path.GetFileName)
.Where(static name => !string.IsNullOrEmpty(name)) .Where(static name => !string.IsNullOrEmpty(name))
.OrderBy(static name => name, StringComparer.OrdinalIgnoreCase) .OrderBy(static name => name, StringComparer.OrdinalIgnoreCase);
.ToArray()!;
return new[] { ".", ".." }.Concat(children).ToArray()!;
} }
private static uint ComputeDirectoryEntryHash(ReadOnlySpan<byte> utf8Name) private static uint ComputeDirectoryEntryHash(ReadOnlySpan<byte> utf8Name)