fix(libkernel): fail open with O_NOFOLLOW on a symbolic link

sceKernelOpen built the host flags from the bits it knows and dropped
O_NOFOLLOW (0x100), so open and sceKernelOpen with O_NOFOLLOW followed
a symbolic link and opened its target, and with O_CREAT created the
missing target of a dangling link.

FreeBSD looks up the last component without following it when
O_NOFOLLOW is set (sys/kern/vfs_vnops.c, vn_open_cred:
"((fmode & O_NOFOLLOW) ? NOFOLLOW : FOLLOW) | LOCKLEAF", and with
O_CREAT "if ((fmode & O_EXCL) == 0 && (fmode & O_NOFOLLOW) == 0)
ndp->ni_cnd.cn_flags |= FOLLOW;"), and then refuses the link itself in
vn_open_vnode: "if (vp->v_type == VLNK) return (EMLINK);".

open now fails with EMLINK when O_NOFOLLOW is set and the path names a
symbolic link. O_CREAT | O_EXCL keeps failing with EEXIST, and a
trailing slash still resolves through the link.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
milescovielloandClaude Opus 5.5 committed 2026-10-11 21:30:23 +00:00
1 parent 9bee015035
commit 9421484572
3 files changed
+16

No files matched your search

@@ -9,6 +9,7 @@ constexpr std::int32_t SCE_KERNEL_O_RDWR = 0x00000002;
constexpr std::int32_t SCE_KERNEL_O_ACCMODE = 0x00000003;
constexpr std::int32_t SCE_KERNEL_O_APPEND = 0x00000008;
constexpr std::int32_t SCE_KERNEL_O_FSYNC = 0x00000080;
constexpr std::int32_t SCE_KERNEL_O_NOFOLLOW = 0x00000100;
constexpr std::int32_t SCE_KERNEL_O_SYNC = 0x00000080;
constexpr std::int32_t SCE_KERNEL_O_CREAT = 0x00000200;
constexpr std::int32_t SCE_KERNEL_O_TRUNC = 0x00000400;
@@ -152,6 +152,11 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) {
if (std::filesystem::exists(status) && !std::filesystem::is_directory(status)) return SceErrorFromErrno(ENOTDIR);
}
#endif
if ((flags & SCE_KERNEL_O_NOFOLLOW) != 0 && (flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) != (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) {
constexpr int GuestEmlink = 31;
std::error_code error;
if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GuestEmlink);
}
int fd = NativeOpen(native, MapFlags(flags), mode);
#ifdef _WIN32
if (fd < 0 && errno != ENOENT) {
+10
View File
@@ -409,6 +409,16 @@ int main() {
const auto dangling = root / "dangling";
std::filesystem::create_symlink("missing.txt", dangling);
Require(lstat_nid_postfix(dangling.string().c_str(), &linkStatus) == 0 && (linkStatus.st_mode & 0170000) == 0120000);
constexpr int noFollow = 0x100;
Require(sceKernelOpen(link.string().c_str(), noFollow, 0) == static_cast<int>(0x8002001Fu));
Require(open_nid_postfix(link.string().c_str(), noFollow, 0) == -1 && *__error_nid_postfix() == 31);
Require(open_nid_postfix(dangling.string().c_str(), noFollow | 0x201, 0600) == -1 && *__error_nid_postfix() == 31);
Require(!std::filesystem::exists(root / "missing.txt"));
Require(open_nid_postfix(dangling.string().c_str(), noFollow | 0xA01, 0600) == -1 && *__error_nid_postfix() == 17);
const int followed = open_nid_postfix(link.string().c_str(), 0, 0);
Require(followed >= 0 && close_nid_postfix(followed) == 0);
const int plain = open_nid_postfix(presentName.c_str(), noFollow, 0);
Require(plain >= 0 && close_nid_postfix(plain) == 0);
Require(stat_nid_postfix(dangling.string().c_str(), &status) == -1 && *__error_nid_postfix() == 2);
Require(lstat_nid_postfix((root / "present.txt" / "child").string().c_str(), &linkStatus) == -1 && *__error_nid_postfix() == 20);
const auto viaMissing = rootName + "/missing/../present.txt";