From 4a0882fc18c062633e82d6926f58f4ecdb002230 Mon Sep 17 00:00:00 2001 From: wonfen Date: Sat, 3 Oct 2026 15:59:39 +0800 Subject: [PATCH] fix(singleton): repair Windows lock file permissions in place --- Changelog.md | 4 ++ src-tauri/src/core/owner_identity.rs | 64 ++++++++++++++++++++-------- src-tauri/src/utils/server.rs | 2 +- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Changelog.md b/Changelog.md index 2e8f46b64..7b3e8cd95 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,10 @@ - 修复从开始菜单再次打开应用后,窗口反复弹出并提示启动失败的问题 +**🖥️ Windows** + +- 修复 Windows 升级后因文件权限异常无法启动、需要手动清理配置的问题 +
diff --git a/src-tauri/src/core/owner_identity.rs b/src-tauri/src/core/owner_identity.rs index 798abe9d6..b730fabbe 100644 --- a/src-tauri/src/core/owner_identity.rs +++ b/src-tauri/src/core/owner_identity.rs @@ -63,8 +63,8 @@ pub(crate) fn open_private_current_user_file(path: &Path) -> Result Result { - windows_owner::open_or_create_private_file(path) +pub(crate) fn open_or_create_current_user_lock(path: &Path) -> Result { + windows_owner::open_or_create_lock_file(path) } /// Repairs an application data root Windows no longer reports as owned by the current user. @@ -110,7 +110,7 @@ mod windows_owner { BY_HANDLE_FILE_INFORMATION, CREATE_NEW, CreateFileW, FILE_ALL_ACCESS, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_DISK, - GetFileInformationByHandle, GetFileType, OPEN_EXISTING, READ_CONTROL, WRITE_DAC, WRITE_OWNER, + GetFileInformationByHandle, GetFileType, OPEN_ALWAYS, OPEN_EXISTING, READ_CONTROL, WRITE_DAC, WRITE_OWNER, }; use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; @@ -215,22 +215,49 @@ mod windows_owner { Ok(file) } - pub(super) fn open_or_create_private_file(path: &Path) -> Result { + pub(super) fn open_or_create_lock_file(path: &Path) -> Result { let sid = current_sid()?; let descriptor = LocalSecurityDescriptor::from_sid(&sid)?; - match create_file(path, &descriptor) { - Ok(file) => Ok(file), - Err(error) - if error - .downcast_ref::() - .is_some_and(|error| error.raw_os_error() == Some(ERROR_FILE_EXISTS as i32)) => - { - let file = open_file(path)?; - validate_private_file(&file, descriptor.owner()?)?; - Ok(file) - } - Err(error) => Err(error), + let attributes = SECURITY_ATTRIBUTES { + nLength: std::mem::size_of::() as u32, + lpSecurityDescriptor: descriptor.0, + bInheritHandle: 0, + }; + let wide = wide_path(path)?; + let handle = unsafe { + CreateFileW( + wide.as_ptr(), + GENERIC_READ | READ_CONTROL | WRITE_DAC, + // Replacing a live lock would let a second instance lock a different file. + FILE_SHARE_READ | FILE_SHARE_WRITE, + &attributes, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, + std::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(std::io::Error::last_os_error()).context("failed to open current-user lock file"); } + let file = unsafe { std::fs::File::from_raw_handle(handle) }; + let mut information = BY_HANDLE_FILE_INFORMATION::default(); + if unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut information) } == 0 { + return Err(std::io::Error::last_os_error()).context("failed to inspect current-user lock file"); + } + if information.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) != 0 + || unsafe { GetFileType(file.as_raw_handle()) } != FILE_TYPE_DISK + || information.nNumberOfLinks != 1 + { + bail!("current-user lock is not an ordinary, single-link file"); + } + let expected_owner = descriptor.owner()?; + if !owner_matches(&file, expected_owner)? { + bail!("current-user lock belongs to a different Windows user"); + } + // Lock contents are never trusted; records and tokens retain their separate validation. + descriptor.apply_dacl(file.as_raw_handle())?; + validate_private_file(&file, expected_owner)?; + Ok(file) } fn create_file(path: &Path, descriptor: &LocalSecurityDescriptor) -> Result { @@ -442,7 +469,8 @@ mod windows_owner { { continue; } - // A DACL that refuses even the owner read settles it just as well. + // Failed inspection does not prove that a live lock belongs to another user. + Err(error) if name == crate::utils::server::INSTANCE_LOCK_FILE => return Err(error), Err(_) => false, }; if !ours { @@ -712,7 +740,7 @@ mod windows_owner { ) }; if status != 0 { - bail!("failed to restrict owner token DACL: Windows error {status}"); + bail!("failed to restrict current-user file DACL: Windows error {status}"); } Ok(()) } diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index 1acd6d8c5..8cf67ffed 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -324,7 +324,7 @@ fn open_instance_lock(path: &Path) -> Result { std::fs::create_dir_all(parent)?; #[cfg(windows)] { - crate::core::owner_identity::open_or_create_private_current_user_file(path) + crate::core::owner_identity::open_or_create_current_user_lock(path) } #[cfg(unix)]