From 992d77eb9027a2482ab92e0018e93242e7ce9bf2 Mon Sep 17 00:00:00 2001 From: mike <219478+ilude@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:14:26 -0400 Subject: [PATCH] Cygwin: fix add_item race in concurrent shared memory initialization When multiple Cygwin/MSYS2 processes start concurrently, create_root_entry() can fail with EPERM because two processes both attempt to populate the mount table in shared memory. user_info::create() calls open_shared(), which internally uses CreateFileMappingW and tracks whether the mapping was newly created or already existed. However, create() discards this information by calling the overload that drops the 'created' output parameter, relying instead on a spinlock in initialize() to coordinate. The spinlock has a 15-second timeout; if the initializing process is slow (e.g., internal_getpwsid triggering domain controller lookups), waiting processes time out and re-initialize, colliding on the immutable "/" mount entry. Use the open_shared() overload that returns the 'created' flag to determine whether this process should initialize the shared memory. Only the process that actually created the file mapping calls initialize(); all others find the mapping already exists and skip initialization. As a safety net, add MOUNT_OVERRIDE to create_root_entry() so that add_item() overwrites an existing "/" entry rather than failing with EPERM. This makes the initialization idempotent even if the primary fix is somehow bypassed. Signed-off-by: Mike Glenn --- winsup/cygwin/mm/shared.cc | 8 +++++--- winsup/cygwin/mount.cc | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/winsup/cygwin/mm/shared.cc b/winsup/cygwin/mm/shared.cc index 7977df382a..83a6f8ea2f 100644 --- a/winsup/cygwin/mm/shared.cc +++ b/winsup/cygwin/mm/shared.cc @@ -221,6 +221,7 @@ void user_info::create (bool reinit) { WCHAR name[UNLEN + 1] = L""; /* Large enough for SID */ + bool created = false; if (reinit) { @@ -236,11 +237,12 @@ user_info::create (bool reinit) user_shared = (user_info *) open_shared (name, USER_VERSION, cygwin_user_h, sizeof (user_info), - SH_USER_SHARED, &sec_none); - debug_printf ("opening user shared for '%W' at %p", name, user_shared); + SH_USER_SHARED, created, &sec_none); + debug_printf ("opening user shared for '%W' at %p, created %d", + name, user_shared, created); ProtectHandleINH (cygwin_user_h); debug_printf ("user shared version %x", user_shared->version); - if (reinit) + if (reinit || created) user_shared->initialize (); cygheap->shared_regions.user_shared_addr = user_shared; } diff --git a/winsup/cygwin/mount.cc b/winsup/cygwin/mount.cc index ff0279336b..dad8c7b078 100644 --- a/winsup/cygwin/mount.cc +++ b/winsup/cygwin/mount.cc @@ -550,7 +550,8 @@ mount_info::create_root_entry (const PWCHAR root) sys_wcstombs (native_root, PATH_MAX, root); assert (*native_root != '\0'); if (add_item (native_root, "/", - MOUNT_SYSTEM | MOUNT_IMMUTABLE | MOUNT_AUTOMATIC | MOUNT_NOACL) + MOUNT_SYSTEM | MOUNT_IMMUTABLE | MOUNT_AUTOMATIC | MOUNT_NOACL + | MOUNT_OVERRIDE) < 0) api_fatal ("add_item (\"%s\", \"/\", ...) failed, errno %d", native_root, errno); /* Create a default cygdrive entry. Note that this is a user entry.