Implement O_NOFOLLOW on Windows without following symlinks

This commit is contained in:
Andrea Mazzoleni 2026-08-31 17:14:34 +02:00
parent f3caf1dbf1
commit 40569fff2f
2 changed files with 115 additions and 7 deletions

View file

@ -1248,6 +1248,8 @@ int windows_open(const char* file, int flags, ...)
wchar_t conv_buf[CONV_MAX];
HANDLE h;
int f;
int truncate;
int delete_on_close;
DWORD access;
DWORD share;
DWORD create;
@ -1270,6 +1272,8 @@ int windows_open(const char* file, int flags, ...)
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
truncate = 0;
delete_on_close = 0;
switch (flags & (O_CREAT | O_EXCL | O_TRUNC)) {
case 0 :
create = OPEN_EXISTING;
@ -1282,10 +1286,20 @@ int windows_open(const char* file, int flags, ...)
create = CREATE_NEW;
break;
case O_CREAT | O_TRUNC :
create = CREATE_ALWAYS;
if ((flags & O_NOFOLLOW) != 0) {
create = OPEN_ALWAYS;
truncate = 1;
} else {
create = CREATE_ALWAYS;
}
break;
case O_TRUNC :
create = TRUNCATE_EXISTING;
if ((flags & O_NOFOLLOW) != 0) {
create = OPEN_EXISTING;
truncate = 1;
} else {
create = TRUNCATE_EXISTING;
}
break;
default :
errno = EINVAL;
@ -1303,8 +1317,16 @@ int windows_open(const char* file, int flags, ...)
attr |= FILE_FLAG_SEQUENTIAL_SCAN;
if ((flags & _O_SHORT_LIVED) != 0)
attr |= FILE_ATTRIBUTE_TEMPORARY;
if ((flags & O_TEMPORARY) != 0)
attr |= FILE_FLAG_DELETE_ON_CLOSE;
if ((flags & O_TEMPORARY) != 0) {
if ((flags & O_NOFOLLOW) != 0) {
access |= DELETE;
delete_on_close = 1;
} else {
attr |= FILE_FLAG_DELETE_ON_CLOSE;
}
}
if ((flags & O_NOFOLLOW) != 0)
attr |= FILE_FLAG_OPEN_REPARSE_POINT;
h = CreateFileW(convert(conv_buf, file), access, share, 0, create, attr, 0);
if (h == INVALID_HANDLE_VALUE) {
@ -1312,8 +1334,94 @@ int windows_open(const char* file, int flags, ...)
return -1;
}
if ((flags & O_NOFOLLOW) != 0) {
BY_HANDLE_FILE_INFORMATION info;
FILE_ATTRIBUTE_TAG_INFO tag;
if (!GetFileInformationByHandle(h, &info)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!GetReparseTagInfoByHandle(h, &tag, info.dwFileAttributes)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if ((tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
if (tag.ReparseTag != IO_REPARSE_TAG_DEDUP) {
CloseHandle(h);
errno = ELOOP;
return -1;
}
/*
* Deduplicated files are reparse points, but they must be opened
* with normal reparse processing to access their logical data.
* Reopening by path intentionally accepts a TOCTOU window only
* for deduplicated files.
*/
if (!CloseHandle(h)) {
windows_errno(GetLastError());
return -1;
}
attr &= ~FILE_FLAG_OPEN_REPARSE_POINT;
h = CreateFileW(convert(conv_buf, file), access, share, 0, create, attr, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
}
}
/*
* With O_NOFOLLOW delete-on-close must be enabled only after the opened
* object has been validated, otherwise closing a rejected reparse point
* could delete it.
*/
if (delete_on_close) {
FILE_DISPOSITION_INFO disposition;
disposition.DeleteFile = TRUE;
if (!SetFileInformationByHandle(h, FileDispositionInfo, &disposition, sizeof(disposition))) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
}
/*
* With O_NOFOLLOW truncation must happen only after the opened object
* has been validated, otherwise a symbolic link target could already
* have been truncated before detecting the link.
*/
if (truncate) {
LARGE_INTEGER pos;
pos.QuadPart = 0;
if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!SetEndOfFile(h)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
}
/* mask out flags unknown by Windows */
flags &= ~(O_DIRECT | O_DSYNC);
flags &= ~(O_DIRECT | O_DSYNC | O_NOFOLLOW);
f = _open_osfhandle((intptr_t)h, flags);
if (f == -1) {

View file

@ -146,8 +146,6 @@ wchar_t* u8tou16_mayfail(wchar_t* conv_buf, size_t number_of_wchar, const char*
#define AT_SYMLINK_NOFOLLOW 1
#undef utimensat
#define utimensat windows_utimensat
#define O_NOFOLLOW 0
#define O_CLOEXEC 0
#define dirent_hidden windows_dirent_hidden
#define HAVE_STRUCT_DIRENT_D_STAT 1
#undef HAVE_STRUCT_DIRENT_D_INO
@ -185,6 +183,8 @@ wchar_t* u8tou16_mayfail(wchar_t* conv_buf, size_t number_of_wchar, const char*
#define HAVE_DIRECT_IO 1
#define O_DIRECT 0x10000000
#define O_DSYNC 0x20000000
#define O_NOFOLLOW 0x40000000
#define O_CLOEXEC 0
#undef gmtime_r
#define gmtime_r windows_gmtime_r
#undef localtime_r