fix(kernel): finish Posix -1/errno for file ops and open EACCES (#567)

Map UnauthorizedAccess on open to PERMISSION_DENIED and route Posix
lseek/pread/pwrite/rename/etc failures through PosixFailure so libc-style
callers get RAX=-1 plus TLS errno, matching open/read/write.
This commit is contained in:
Radu Ursache
2026-07-23 15:32:56 +03:00
committed by GitHub
parent d7bd814fb9
commit 956da769a3
3 changed files with 185 additions and 13 deletions
@@ -125,6 +125,125 @@ public sealed class KernelMemoryCompatExportsTests
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
}
// FreeBSD/PS4 EBADF; PosixFailure maps ORBIS NOT_FOUND on fd calls to this.
private const int Ebadf = 9;
// FreeBSD/PS4 EACCES; PosixFailure maps ORBIS PERMISSION_DENIED to this.
private const int Eacces = 13;
// TLS slot used by KernelRuntimeCompatExports.TrySetErrno (FsBase + 0x40).
private const ulong TlsErrnoOffset = 0x40;
[Fact]
public void PosixLseek_BadDescriptorReturnsMinusOneWithEbadf()
{
const ulong memoryBase = 0x1_0000_0000;
const ulong fsBase = memoryBase + 0x100;
var memory = new FakeCpuMemory(memoryBase, 0x1000);
var context = new CpuContext(memory, Generation.Gen5)
{
FsBase = fsBase,
};
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = 0; // SEEK_SET
var result = KernelMemoryCompatExports.PosixLseek(context);
Assert.Equal(-1, result);
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
Assert.Equal(Ebadf, ReadErrno(memory, fsBase));
}
[Fact]
public void PosixPread_BadDescriptorReturnsMinusOneWithEbadf()
{
const ulong memoryBase = 0x1_0000_0000;
const ulong bufferAddress = memoryBase + 0x200;
const ulong fsBase = memoryBase + 0x100;
var memory = new FakeCpuMemory(memoryBase, 0x1000);
var context = new CpuContext(memory, Generation.Gen5)
{
FsBase = fsBase,
};
context[CpuRegister.Rdi] = 0x80020002; // never-opened / sentinel fd
context[CpuRegister.Rsi] = bufferAddress;
context[CpuRegister.Rdx] = 0x40;
context[CpuRegister.Rcx] = 0; // offset
var result = KernelMemoryCompatExports.PosixPread(context);
Assert.Equal(-1, result);
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
Assert.Equal(Ebadf, ReadErrno(memory, fsBase));
}
[Fact]
public void KernelPread_BadDescriptorStillReturnsOrbisNotFound()
{
// sceKernel* entry points keep the raw Orbis ABI; only Posix* maps to -1/errno.
const ulong memoryBase = 0x1_0000_0000;
const ulong bufferAddress = memoryBase + 0x200;
var memory = new FakeCpuMemory(memoryBase, 0x1000);
var context = new CpuContext(memory, Generation.Gen5);
context[CpuRegister.Rdi] = 0x80020002;
context[CpuRegister.Rsi] = bufferAddress;
context[CpuRegister.Rdx] = 0x40;
context[CpuRegister.Rcx] = 0;
var result = KernelMemoryCompatExports.KernelPread(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND, result);
}
[Fact]
public void PosixOpen_MutatingApp0ReturnsMinusOneWithEacces()
{
// /app0 is read-only for mutating opens (retail semantics). That path
// returns PERMISSION_DENIED which PosixFailure maps to EACCES - the same
// errno UnauthorizedAccessException open failures now produce.
var tempRoot = Path.Combine(
Path.GetTempPath(),
$"sharpemu-posix-open-eacces-{Guid.NewGuid():N}");
var app0Root = Path.Combine(tempRoot, "app0");
Directory.CreateDirectory(app0Root);
KernelMemoryCompatExports.RegisterGuestPathMount("/app0", app0Root);
try
{
const ulong memoryBase = 0x1_0000_0000;
const ulong pathAddress = memoryBase + 0x200;
const ulong fsBase = memoryBase + 0x100;
var memory = new FakeCpuMemory(memoryBase, 0x1000);
var context = new CpuContext(memory, Generation.Gen5)
{
FsBase = fsBase,
};
memory.WriteCString(pathAddress, "/app0/readonly-create.bin");
context[CpuRegister.Rdi] = pathAddress;
context[CpuRegister.Rsi] = 0x0201; // O_WRONLY | O_CREAT
var result = KernelMemoryCompatExports.PosixOpen(context);
Assert.Equal(-1, result);
Assert.Equal(ulong.MaxValue, context[CpuRegister.Rax]);
Assert.Equal(Eacces, ReadErrno(memory, fsBase));
}
finally
{
KernelMemoryCompatExports.UnregisterGuestPathMount("/app0");
if (Directory.Exists(tempRoot))
{
Directory.Delete(tempRoot, recursive: true);
}
}
}
private static int ReadErrno(FakeCpuMemory memory, ulong fsBase)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
Assert.True(memory.TryRead(fsBase + TlsErrnoOffset, bytes));
return BitConverter.ToInt32(bytes);
}
[Fact]
public void Sprintf_ReadsVariadicDoubleFromXmmRegister()
{