Skip to content

MDEV-39533 my_realpath and MY_NOSYMLINKS on Windows - #5682

Open
vaintroub wants to merge 1 commit into
11.4from
11.4-MDEV-39533
Open

vaintroub wants to merge 1 commit into
11.4from
11.4-MDEV-39533

Conversation

@vaintroub

@vaintroub vaintroub commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Summary

On Windows, my_realpath() only called GetFullPathName(), which
canonicalizes ., .. and drive letters but does not resolve NTFS
symlinks, junctions or mount points (unlike POSIX realpath()). At
the same time, my_open() and my_delete() ignored MY_NOSYMLINKS
entirely, so the symlink-attack protection used for MyISAM/Aria's
DATA DIRECTORY/INDEX DIRECTORY (mi_open()/ma_open(),
my_handler_delete_with_symlink()) was silently absent on Windows.

  • Fix my_realpath() to actually resolve reparse points, using
    CreateFile() + GetFinalPathNameByHandle(). A missing path now
    correctly returns 1/ENOENT on Windows too (previously always
    returned 0), matching POSIX realpath().
  • Make my_open()/my_delete() honor MY_NOSYMLINKS on Windows by
    comparing the caller-supplied (expected already-resolved) name
    against the actually-opened handle's resolved path, mirroring the
    realpath()-equality branch of the POSIX NOSYMLINK_FUNCTION_BODY
    macro (same ENOTDIR errno for a mismatch).
  • GetFinalPathNameByHandle(FILE_NAME_NORMALIZED) can fail on some
    SMB shares; both my_realpath() and MY_NOSYMLINKS verification
    fall back to the weaker FILE_NAME_OPENED query in that case, and
    log a one-time warning naming the affected path.
  • MyISAM/Aria's data-file open only requested MY_NOSYMLINKS when
    O_NOFOLLOW was set, and O_NOFOLLOW was plain 0 on Windows, so
    the data file was never actually protected (only the index file
    was). Give O_NOFOLLOW a real, reserved bit on Windows so that
    gating works as intended.
  • Rewrite my_win_unlink() (moved from my_delete.c to
    my_winfile.cc): verify no symlinks before any delete disposition
    is set (closes a window where a rejected delete could still commit
    via delete-on-close), rename through the handle rather than by
    path, and report real posix-delete errors (e.g. a read-only file)
    instead of masking them with a fallback attempt.

Test plan

  • unittest/mysys/my_symlink-t (17/17): junction resolution and
    MY_NOSYMLINKS enforcement for my_open()/my_delete().
  • unittest/mysys/my_delete-t (31/31): my_win_unlink()'s own
    fallback paths (posix-semantics failure, rename failure,
    read-only files, orphan-rename-back).
  • Full main mtr suite (1195/1195) and the full buildbot-matching
    suite set (vcol,gcol,period,perfschema,parts,main,innodb, versioning,plugins,mariabackup,roles,auth_gssapi,mysql_sha2, rocksdb,sysschema, 2641/2655 -- the 14 failures are unrelated
    innochecksum-tool and audit/error-log-plugin tests, verified
    against an unmodified baseline build).

🤖 Generated with Claude Code

@vaintroub
vaintroub requested a lite review from Copilot September 15, 2026 17:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical MY_NOSYMLINKS bypasses and unsafe post-open validation remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)

Comment thread mysys/my_delete.c Outdated
Comment thread mysys/my_winfile.cc Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical deletion-safety issues and Windows path-resolution contract findings remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (2)

Comment thread mysys/my_delete.c Outdated
Comment on lines +177 to +180
if (win_unlink_check_nosymlinks(handle, name, MyFlags))
{
CloseHandle(handle);
DBUG_RETURN(-1); /* errno set by my_win_verify_nosymlinks() */
Comment thread mysys/my_delete.c Outdated
Comment on lines +209 to +213
if (win_unlink_check_nosymlinks(handle, name, MyFlags))
{
CloseHandle(handle);
DBUG_RETURN(-1); /* errno set by my_win_verify_nosymlinks() */
}
@vaintroub
vaintroub force-pushed the 11.4-MDEV-39533 branch 2 times, most recently from 5a514a3 to 49f9c91 Compare October 2, 2026 23:17
@vaintroub
vaintroub requested a lite review from Copilot October 2, 2026 23:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The fallback can undermine symlink protection, and debug Windows builds have a compilation error.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)

Comment thread mysys/my_winfile.cc Outdated
Comment on lines +421 to +425
/* NORMALIZED fails outright on some SMB servers; OPENED still names
the same resolved file (just a possibly different spelling), so
it's a safe fallback even for MY_NOSYMLINKS verification. */
len= GetFinalPathNameByHandle(handle, buf, (DWORD) sizeof(buf),
FILE_NAME_OPENED | VOLUME_NAME_DOS);
Comment thread mysys/my_winfile.cc
Comment on lines +680 to +681
DBUG_EXECUTE_IF("force_posix_delete_fail", goto nonposix_delete;);
FILE_DISPOSITION_INFO_EX disp_ex;

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread mysys/my_winfile.cc
Comment on lines +436 to +440
/* NORMALIZED fails outright on some SMB servers; fall back to OPENED
so such shares remain usable, though it may not fully resolve
reparse points, weakening MY_NOSYMLINKS verification. */
warn_final_path_fallback_once(name);
len= GetFinalPathNameByHandle(handle, buf, (DWORD) sizeof(buf),
Comment thread mysys/my_winfile.cc Outdated
Comment on lines +646 to +649
falls back to renaming to a unique name, then marking for classic delete-on-close.
The fallback is needed for FAT32/ExFAT, which do not have "POSIX-semantics" delete.
Renaming to unique name is needed, otherwise recreating file with the same name might
fail if another handle on file is still open.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread mysys/my_winfile.cc
*/
DBUG_EXECUTE_IF("force_posix_delete_fail", goto nonposix_delete;);
FILE_DISPOSITION_INFO_EX disp_ex;
disp_ex.Flags= FILE_DISPOSITION_FLAG_DELETE|FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;
@vaintroub
vaintroub force-pushed the 11.4-MDEV-39533 branch 2 times, most recently from 675880d to 1aceefa Compare October 9, 2026 23:16
On Windows, my_realpath() only called GetFullPathName(), which doesn't
resolve symlinks, junctions or mount points, unlike POSIX realpath().
At the same time, my_open() and my_delete() ignored MY_NOSYMLINKS
entirely, so Windows silently lacked the symlink-attack protection
MyISAM/Aria rely on for DATA DIRECTORY/INDEX DIRECTORY.

Fix my_realpath() to resolve reparse points via CreateFile() +
GetFinalPathNameByHandle(). A missing path now correctly returns
1/ENOENT on Windows too, matching POSIX.

Make my_open()/my_delete() honor MY_NOSYMLINKS by comparing the
caller-supplied (already-resolved) name against the opened handle's
resolved path, mirroring the realpath()-equality branch of POSIX's
NOSYMLINK_FUNCTION_BODY macro, rejecting a mismatch with ENOTDIR.

Known limitation: GetFinalPathNameByHandle(FILE_NAME_NORMALIZED) can
fail on some SMB shares; both my_realpath() and MY_NOSYMLINKS then
fall back to the weaker FILE_NAME_OPENED query, logging a one-time
warning naming the affected path.

O_NOFOLLOW was plain 0 on Windows, so MyISAM/Aria's data-file open
never actually got MY_NOSYMLINKS (only the index file did); it's now a
real, reserved bit so that gating works as intended.

my_win_unlink() (moved from my_delete.c to my_winfile.cc) now verifies
no symlinks before any delete disposition is set, renames through the
handle rather than by path, and reports real posix-delete errors (e.g.
a read-only file) instead of masking them with a fallback attempt.

Add unittest/mysys/my_symlink-t.c and extend my_delete-t.c with
my_win_unlink()'s own fallback paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants