Skip to content

fix: fix TOCTOU race in wallpaper save function#1145

Merged
xionglinlin merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:master
Jun 16, 2026
Merged

fix: fix TOCTOU race in wallpaper save function#1145
xionglinlin merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:master

Conversation

@xionglinlin

@xionglinlin xionglinlin commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
  1. Use unix.Open with O_NOFOLLOW to atomically reject symlinks, eliminating TOCTOU race condition
  2. Reuse the file descriptor for stat, MD5 checksum calculation, and content reading
  3. Compute MD5 from already-read content instead of re-opening the file
  4. Replace runuser/cat shell command with direct file read via the file descriptor
  5. Close file descriptor properly with defer

Log: Fixed security vulnerability in wallpaper saving functionality

Influence:

  1. Test saving wallpaper with a regular file to verify functionality
  2. Test with a symlink target to confirm symlinks are rejected
  3. Verify MD5 checksum is computed correctly
  4. Test file permission handling and error cases
  5. Verify the fix works for both regular and solid color wallpapers
  6. Test concurrent operations to ensure race condition is mitigated

fix: 修复壁纸保存函数中的TOCTOU竞态漏洞

  1. 使用unix.Open函数配合O_NOFOLLOW标志原子性地拒绝符号链接,消除TOCTOU竞 态条件
  2. 复用文件描述符进行stat、MD5校验和计算及内容读取
  3. 从已读取的内容计算MD5,避免重新打开文件
  4. 使用文件描述符直接读取替换runuser/cat shell命令
  5. 使用defer正确关闭文件描述符

Log: 修复壁纸保存功能的安全漏洞

Influence:

  1. 使用常规文件测试保存壁纸功能,验证功能正常
  2. 使用符号链接目标测试,确认符号链接被拒绝
  3. 验证MD5校验和计算是否正确
  4. 测试文件权限处理和错误情况
  5. 验证修复对常规壁纸和纯色壁纸均有效
  6. 测试并发操作以确保竞态条件已被解决

PMS: BUG-364751
Change-Id: I0ed2d3474f0a869e4f61731f53730f6103720785

Summary by Sourcery

Mitigate a TOCTOU race and improve security in the custom wallpaper saving path by hardening file handling and checksum computation.

Bug Fixes:

  • Prevent TOCTOU-based symlink attacks in the wallpaper save function by atomically rejecting symlinks and avoiding re-open races.

Enhancements:

  • Reuse a single file descriptor for stat, content reading, and MD5 computation, and replace an external runuser/cat invocation with direct file reads to simplify and harden the code.

@sourcery-ai

sourcery-ai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Refactors the wallpaper-saving path to safely open the source file using unix.Open with O_NOFOLLOW, reuse the resulting file descriptor for stat, content read, and MD5 hashing, and remove the runuser/cat shell pipeline, thereby fixing a TOCTOU race and tightening permissions handling.

Sequence diagram for updated SaveCustomWallPaper file handling

sequenceDiagram
    participant Daemon
    participant SaveCustomWallPaper
    participant unix
    participant osFile
    participant io
    participant md5
    participant os

    Daemon->>SaveCustomWallPaper: SaveCustomWallPaper(sender, username, file)
    SaveCustomWallPaper->>unix: Open(file, O_RDONLY|O_NOFOLLOW|O_CLOEXEC, 0)
    unix-->>SaveCustomWallPaper: fd
    SaveCustomWallPaper->>osFile: NewFile(uintptr(fd), file)
    SaveCustomWallPaper->>osFile: Stat()
    osFile-->>SaveCustomWallPaper: FileInfo
    SaveCustomWallPaper->>io: ReadAll(osFile)
    io-->>SaveCustomWallPaper: src
    SaveCustomWallPaper->>md5: Sum(src)
    md5-->>SaveCustomWallPaper: md5sum
    SaveCustomWallPaper->>os: WriteFile(destFile, src, 0644)
    os-->>SaveCustomWallPaper: err
    SaveCustomWallPaper-->>Daemon: destFile / error
Loading

File-Level Changes

Change Details Files
Harden wallpaper source file handling by opening via unix.Open with O_NOFOLLOW and reusing the resulting file descriptor for subsequent operations.
  • Import golang.org/x/sys/unix to access unix.Open and related flags.
  • Replace os.Stat(file) with unix.Open using O_RDONLY
O_NOFOLLOW
Compute MD5 and copy wallpaper contents directly from the opened file instead of re-opening or shelling out as another user.
  • Read the entire file content via io.ReadAll(f) once and reuse the resulting byte slice.
  • Compute the MD5 checksum from the in-memory content using md5.Sum and fmt.Sprintf instead of dutils.SumFileMd5.
  • Remove the runuser/cat exec pipeline and rely on the already-read content as the source for os.WriteFile.
  • Preserve existing destination path logic and write the content to destFile with os.WriteFile(destFile, src, 0644).
bin/dde-system-daemon/wallpaper.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • By replacing the runuser/cat call with a direct read from the daemon process, the effective permissions check has changed; consider whether the daemon should still enforce that username has read access to the source file (e.g., via a dedicated helper or explicit ACL check) to avoid unintentionally broadening access to files.
  • The new approach reads the entire wallpaper file into memory to compute MD5 and then writes it out; if wallpapers can be large, consider switching to a streaming hash and copy (e.g., using io.Copy with md5.New) to avoid holding the whole file in memory at once.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- By replacing the `runuser`/`cat` call with a direct read from the daemon process, the effective permissions check has changed; consider whether the daemon should still enforce that `username` has read access to the source file (e.g., via a dedicated helper or explicit ACL check) to avoid unintentionally broadening access to files.
- The new approach reads the entire wallpaper file into memory to compute MD5 and then writes it out; if wallpapers can be large, consider switching to a streaming hash and copy (e.g., using `io.Copy` with `md5.New`) to avoid holding the whole file in memory at once.

## Individual Comments

### Comment 1
<location path="bin/dde-system-daemon/wallpaper.go" line_range="225-228" />
<code_context>
 	}
-	info, err := os.Stat(file)
+	// Use O_NOFOLLOW to atomically reject symlinks, eliminating TOCTOU race.
+	fd, err := unix.Open(file, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
+	if err != nil {
+		logger.Warning(err)
+		return "", dbusutil.ToError(err)
+	}
+	f := os.NewFile(uintptr(fd), file)
</code_context>
<issue_to_address>
**🚨 issue (security):** Opening the file as the daemon user changes the security model compared to the previous `runuser`-based read.

With the prior `runuser` approach, the kernel enforced the target user's filesystem permissions and returned "permission denied" when appropriate. Now the daemon opens the file itself, likely with broader privileges, so per-user filesystem checks are no longer applied at this layer. Please confirm this change is deliberate and that higher-level authorization fully replaces the lost OS‑level permission check for this path.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread bin/dde-system-daemon/wallpaper.go Outdated
@xionglinlin xionglinlin force-pushed the master branch 2 times, most recently from 9d1602b to 3ec4a8f Compare June 15, 2026 09:45
@xionglinlin xionglinlin requested a review from wubw0508 June 16, 2026 02:10
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:100分

■ 【总体评价】

代码完美修复了TOCTOU竞态条件漏洞,将文件检查与读取操作通过文件描述符原子化
逻辑严谨且无任何安全缺陷,实现方式符合系统级安全编程最佳实践

■ 【详细分析】

  • 1.语法逻辑(完全正确)✓

使用unix.Open获取文件描述符后,所有后续的Stat检查与io.Copy读取均基于该fd,彻底消除了检查与使用之间的时间窗口。SaveCustomWallPaper函数中修复了原先GetUserDirs返回值被忽略的问题,改为正确处理err,避免了潜在的空指针解引用风险。
建议:无

  • 2.代码质量(优秀)✓

将核心安全防御逻辑下沉到独立的dde-wallpaper-helper程序中,遵循了最小权限原则与职责单一原则。注释精准解释了O_NOFOLLOW的核心防御作用,变量命名规范,错误处理链路清晰。
建议:无

  • 3.代码性能(高效)✓

原实现在计算md5和读取文件内容时进行了两次独立的磁盘I/O操作,重构后通过单次读取将数据加载到内存,随后在内存中直接计算md5并写入目标文件,减少了一次完整的磁盘读取与系统调用开销。
建议:无

  • 4.代码安全(存在0个安全漏洞)✓

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个
成功使用O_NOFOLLOW标志原子性拒绝符号链接,结合runuser进行权限降级,并通过IsRegular和maxSize限制了文件类型与大小,彻底阻断了原有的本地权限提升攻击面。exec.Command采用参数列表形式硬编码执行路径,完全避免了命令注入风险。
建议:无

■ 【改进建议代码示例】

// 在 readWallpaperSourceAsUser 函数入口处增加对空路径的防御性检查,
// 避免因 D-Bus 接口传入空字符串而创建无意义的子进程,进一步提升健壮性。
func readWallpaperSourceAsUser(username string, file string) ([]byte, error) {
	if file == "" {
		return nil, fmt.Errorf("file path cannot be empty")
	}
	
	cmd := exec.Command("runuser", "-u", username, "--", wallpaperHelperPath, file)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	src, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("permission denied, %s is not allowed to read this file:%s: %s", username, file, strings.TrimSpace(stderr.String()))
	}
	return src, nil
}

1. Add dedicated helper binary (dde-wallpaper-helper) to read files safely
   with O_NOFOLLOW, eliminating TOCTOU race condition
2. Execute helper via runuser to drop privileges, so kernel enforces the
   target user's file permissions instead of root bypassing them
3. All file operations (Open/Stat/Read) use the same file descriptor in
   the helper, leaving zero TOCTOU window
4. Compute MD5 from already-read content instead of re-opening the file
5. Remove unused maxSize constant and dead checkFileReadPermission code

Log: Fixed security vulnerability in wallpaper saving functionality.
     The original TOCTOU fix introduced a privilege-escalation regression
     (root reading arbitrary user files). This is now resolved by a
     dedicated helper binary that runs under the target user's identity.

Influence:
1. Test saving wallpaper with a regular file to verify functionality
2. Test with a symlink target to confirm symlinks are rejected
3. Test with a file not readable by the target user to confirm access denied
4. Verify MD5 checksum is computed correctly
5. Verify the fix works for both regular and solid color wallpapers
6. Test concurrent operations to ensure race condition is mitigated

fix: 修复壁纸保存函数中的TOCTOU竞态漏洞

1. 新增专用 helper 二进制 (dde-wallpaper-helper),使用 O_NOFOLLOW 安全读取
   文件,消除 TOCTOU 竞态条件
2. 通过 runuser 以目标用户身份执行 helper,由内核强制文件权限检查,解决
   root 越权读取用户文件的问题
3. helper 内所有文件操作 (Open/Stat/Read) 基于同一文件描述符,TOCTOU 窗口为零
4. 从已读取的内存数据计算 MD5,避免重复打开文件
5. 移除不再使用的 maxSize 常量和 checkFileReadPermission 死代码

Log: 修复壁纸保存功能的安全漏洞。
     原 TOCTOU 修复引入了 root 越权读用户文件的权限回退问题,
     现通过专用 helper 二进制以目标用户身份运行来解决。

Influence:
1. 使用常规文件测试保存壁纸功能,验证功能正常
2. 使用符号链接目标测试,确认符号链接被拒绝
3. 使用目标用户无读权限的文件测试,确认返回权限拒绝
4. 验证 MD5 校验和计算是否正确
5. 验证修复对常规壁纸和纯色壁纸均有效
6. 测试并发操作以确保竞态条件已被解决

PMS: BUG-364751
Change-Id: I0ed2d3474f0a869e4f61731f53730f6103720785
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fly602, mhduiy, wubw0508, xionglinlin

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@xionglinlin xionglinlin merged commit 40b43f9 into linuxdeepin:master Jun 16, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants