Skip to content

fix: harden udev rule file permission and durability on write - #1223

Merged
fly602 merged 1 commit into
linuxdeepin:masterfrom
fly602:master
Aug 27, 2026
Merged

fix: harden udev rule file permission and durability on write#1223
fly602 merged 1 commit into
linuxdeepin:masterfrom
fly602:master

Conversation

@fly602

@fly602 fly602 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor
  1. Replace os.Create with os.OpenFile(..., 0644) to avoid 0666 permission regression under a permissive umask
  2. Add explicit f.Chmod(0644) to narrow permissions on existing files that may have been widened externally
  3. fsync parent directory after first creation so the directory entry survives a forced power loss, not just file data

Log: Fix touchpad udev rule file losing durability and permission guarantees introduced by the fsync refactor

Influence:

  1. Disable touchpad, power off forcibly, reboot and verify the udev rule file still exists and the touchpad stays disabled
  2. Check the permission of /etc/udev/rules.d/90-dde-touchpad.rules is 0644 with a permissive umask (e.g. 0000) set on the service
  3. Toggle touchpad enable/disable repeatedly and confirm no errors in dde-system-daemon logs

fix: 加固触控板 udev 规则文件写入的权限与落盘耐久性

  1. 用 os.OpenFile(..., 0644) 替换 os.Create,避免在宽松 umask 下 0666 导致规则文件可被组或全局写入
  2. 新增 f.Chmod(0644) 兜底,收窄已被外部改宽的已存在文件权限
  3. 首次创建文件后 fsync 父目录,确保目录项在断电时也能落盘

Log: 修复 fsync 重构引入的触控板 udev 规则文件权限退化与
断电后目录项丢失问题

Influence:

  1. 禁用触控板后强制断电重启,验证 udev 规则文件仍存在且触控板保持禁用
  2. 在服务设置宽松 umask(如 0000)下检查 /etc/udev/rules.d/90-dde-touchpad.rules 权限为 0644
  3. 反复启用/禁用触控板,确认 dde-system-daemon 日志无报错

PMS: BUG-374789

Summary by Sourcery

Harden touchpad udev rule updates to preserve file permissions and durability while avoiding unnecessary device refreshes.

Bug Fixes:

  • Preserve touchpad udev rule files across forced power loss by syncing the parent directory after first creation.
  • Prevent udev rule permissions from widening under permissive umasks or external changes by enforcing 0644 on new and existing files.

Enhancements:

  • Avoid unnecessary rule rewrites and device refreshes when the existing rule content is unchanged.

@sourcery-ai

sourcery-ai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Reviewer's Guide

The touchpad udev rule write path now explicitly enforces 0644 permissions and syncs both file data and, on first creation, the parent directory. This prevents permissive umasks or widened existing modes from weakening the rule while improving survival across forced power loss.

Sequence diagram for durable touchpad udev rule creation

sequenceDiagram
    participant Touchpad
    participant FS as FileSystem
    participant RuleFile as UdevRuleFile
    participant ParentDir as ParentDirectory

    Touchpad->>FS: os.Stat(udevRuleFile)
    FS-->>Touchpad: isNew
    Touchpad->>FS: os.OpenFile(udevRuleFile, O_WRONLY|O_CREATE|O_TRUNC, 0644)
    FS-->>Touchpad: f
    Touchpad->>RuleFile: f.Chmod(0644)
    Touchpad->>RuleFile: f.Write(udevRuleContent)
    Touchpad->>RuleFile: f.Sync()
    Touchpad->>RuleFile: f.Close()
    alt isNew
        Touchpad->>FS: os.Open(filepath.Dir(udevRuleFile))
        FS-->>Touchpad: dir
        Touchpad->>ParentDir: dir.Sync()
        Touchpad->>ParentDir: dir.Close()
    end
Loading

File-Level Changes

Change Details Files
Harden udev rule creation and updates against permissive umasks and externally broadened permissions.
  • Replace default file creation with explicit 0644 mode.
  • Apply Chmod(0644) before writing, including for existing files.
  • Preserve existing content-based write skipping behavior.
system/inputdevices1/touchpad.go
Strengthen crash and power-loss durability for newly created rule files.
  • Continue syncing file contents before close.
  • Detect first creation and sync the parent directory so the directory entry is durable.
  • Propagate failures from permission, file sync, directory open/sync, and close paths.
system/inputdevices1/touchpad.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 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="system/inputdevices1/touchpad.go" line_range="152-154" />
<code_context>
 			logger.Debug("udev rule file already exists with correct content, skip writing")
 			return nil
 		}
+		// 标记是否为首次创建,用于后续同步父目录元数据
+		_, statErr := os.Stat(udevRuleFile)
+		isNew := os.IsNotExist(statErr)

-		// 创建或覆盖 udev 规则文件,使用 fsync 确保落盘,
-		// 防止强制关机(断电)时 page cache 丢失导致规则文件丢失
-		f, err := os.Create(udevRuleFile)
+		// 创建或覆盖 udev 规则文件,显式指定 0644 权限,
+		// 防止 os.Create 默认 0666 在宽松 umask 下导致规则文件可被组或全局写入
+		f, err := os.OpenFile(udevRuleFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
 		if err != nil {
 			return err
 		}
+		// 兜底已存在文件权限被改宽的情况
+		if err := f.Chmod(0644); err != nil {
+			f.Close()
+			return err
</code_context>
<issue_to_address>
**🚨 issue (security):** The correct-content early return skips `f.Chmod(0644)`, so an existing rule file with the expected contents but widened permissions remains group- or world-writable.

**Triggers:** When `/etc/udev/rules.d/90-dde-touchpad.rules` has correct contents but mode 0666 or otherwise broader than 0644.

**Suggested fix:** Check and repair the file mode before returning from the correct-content fast path, or perform the permission normalization independently of the content comparison.

```suggestion
		existingContent, err := os.ReadFile(udevRuleFile)
		if err == nil && string(existingContent) == udevRuleContent {
			if err := os.Chmod(udevRuleFile, 0644); err != nil {
				return err
			}
			logger.Debug("udev rule file already exists with correct content, skip writing")
			return nil
		}
```
</issue_to_address>

### Comment 2
<location path="system/inputdevices1/touchpad.go" line_range="156-164" />
<code_context>
 			return nil
 		}
+		// 标记是否为首次创建,用于后续同步父目录元数据
+		_, statErr := os.Stat(udevRuleFile)
+		isNew := os.IsNotExist(statErr)

-		// 创建或覆盖 udev 规则文件,使用 fsync 确保落盘,
</code_context>
<issue_to_address>
**issue (bug_risk):** The `os.Stat`/`os.OpenFile` sequence is racy: if the file exists during `os.Stat` but is removed before `os.OpenFile`, the file is newly created with `isNew == false` and the parent directory is never synced.

**Triggers:** When another process or concurrent enable operation removes the rule file between `os.Stat` and `os.OpenFile`.

**Suggested fix:** Determine whether creation occurred atomically, for example by using an exclusive-create attempt or otherwise synchronizing the check and creation before deciding whether to sync the directory.

```suggestion
		// 创建或覆盖 udev 规则文件,显式指定 0644 权限,
		// 防止 os.Create 默认 0666 在宽松 umask 下导致规则文件可被组或全局写入
		f, err := os.OpenFile(udevRuleFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
		isNew := err == nil
		if err != nil && !os.IsExist(err) {
			return err
		}
		if os.IsExist(err) {
			f, err = os.OpenFile(udevRuleFile, os.O_WRONLY|os.O_TRUNC, 0644)
		}
		if err != nil {
			return err
		}
```
</issue_to_address>

### Comment 3
<location path="system/inputdevices1/touchpad.go" line_range="187-196" />
<code_context>
 			return err
 		}
+		// 首次创建文件时,还需 fsync 父目录以确保目录项落盘,
+		// 否则断电后文件数据虽已持久但目录项丢失,规则文件仍会缺失
+		if isNew {
+			dir, err := os.Open(filepath.Dir(udevRuleFile))
+			if err != nil {
+				return err
+			}
+			err = dir.Sync()
</code_context>
<issue_to_address>
**issue (bug_risk):** If opening or syncing the parent directory fails after the file has been written and closed, the function returns an error, but a retry sees the same content and returns at the earlier fast path without retrying the directory sync, so the creation durability guarantee is permanently skipped.

**Triggers:** When the first directory open or `dir.Sync()` fails and the operation is retried with unchanged rule content.

**Suggested fix:** Record or otherwise preserve the need for a directory sync across retries, or avoid treating an unchanged file as fully handled until its directory-entry durability has been established.
</issue_to_address>

### Comment 4
<location path="system/inputdevices1/touchpad.go" line_range="159-166" />
<code_context>
-		// 创建或覆盖 udev 规则文件,使用 fsync 确保落盘,
-		// 防止强制关机(断电)时 page cache 丢失导致规则文件丢失
-		f, err := os.Create(udevRuleFile)
+		// 创建或覆盖 udev 规则文件,显式指定 0644 权限,
+		// 防止 os.Create 默认 0666 在宽松 umask 下导致规则文件可被组或全局写入
+		f, err := os.OpenFile(udevRuleFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
 		if err != nil {
 			return err
 		}
+		// 兜底已存在文件权限被改宽的情况
+		if err := f.Chmod(0644); err != nil {
+			f.Close()
+			return err
</code_context>
<issue_to_address>
**issue (bug_risk):** `os.OpenFile` truncates an existing rule before `f.Chmod(0644)` runs; if `Chmod` fails, the function returns an error with the previous valid rule content already erased.

**Triggers:** When the filesystem or file metadata prevents changing the mode after the writable file has been opened.

**Suggested fix:** Avoid truncating until permission normalization succeeds, or write the new content to a temporary file and atomically replace the rule only after all preparatory operations succeed.
</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 system/inputdevices1/touchpad.go Outdated
Comment thread system/inputdevices1/touchpad.go Outdated
Comment thread system/inputdevices1/touchpad.go Outdated
Comment thread system/inputdevices1/touchpad.go Outdated
@fly602
fly602 force-pushed the master branch 2 times, most recently from 26465ff to d058609 Compare August 26, 2026 07:54
1. Replace os.Create with os.OpenFile(..., 0644) to avoid 0666
   permission regression under a permissive umask
2. Add explicit f.Chmod(0644) to narrow permissions on existing
   files that may have been widened externally
3. fsync parent directory after first creation so the directory
   entry survives a forced power loss, not just file data
4. Repair file mode on the content-match fast path so existing
   rule files with widened permissions are still narrowed to 0644
5. Use O_EXCL to atomically detect first creation, avoiding the
   Stat/OpenFile TOCTOU race that could skip the parent directory
   sync when the file is removed concurrently
6. Truncate only after Chmod succeeds, so a Chmod failure cannot
   erase the previous valid rule content via O_TRUNC
7. Best-effort fsync the parent directory on the fast path to
   recover when a prior dir.Sync failed and the call is retried
8. Split setTouchpadEnableViaUdev into writeUdevRuleFile and
   syncDirBestEffort for readability without changing behavior

Log: Fix touchpad udev rule file losing durability and permission
guarantees introduced by the fsync refactor

Influence:
1. Disable touchpad, power off forcibly, reboot and verify the udev
   rule file still exists and the touchpad stays disabled
2. Check the permission of /etc/udev/rules.d/90-dde-touchpad.rules
   is 0644 with a permissive umask (e.g. 0000) set on the service
3. Toggle touchpad enable/disable repeatedly and confirm no errors
   in dde-system-daemon logs

fix: 加固触控板 udev 规则文件写入的权限与落盘耐久性

1. 用 os.OpenFile(..., 0644) 替换 os.Create,避免在宽松 umask
   下 0666 导致规则文件可被组或全局写入
2. 新增 f.Chmod(0644) 兜底,收窄已被外部改宽的已存在文件权限
3. 首次创建文件后 fsync 父目录,确保目录项在断电时也能落盘
4. 内容匹配的 fast path 同样执行 Chmod(0644),修复已存在但权限
   被改宽的规则文件跳过权限收窄的问题
5. 用 O_EXCL 原子探测首次创建,消除 Stat/OpenFile 之间文件被
   并发删除导致 isNew 误判、父目录漏 sync 的 TOCTOU 竞态
6. 先 Chmod 成功再 Truncate,避免 Chmod 失败时 O_TRUNC 已清空
   原有效规则内容
7. fast path 补 best-effort 父目录 fsync,覆盖上次创建时
   dir.Sync 失败后重试永久跳过目录项持久化的场景
8. 将 setTouchpadEnableViaUdev 拆分为 writeUdevRuleFile 与
   syncDirBestEffort,提升可读性,行为不变

Log: 修复 fsync 重构引入的触控板 udev 规则文件权限退化与
断电后目录项丢失问题

Influence:
1. 禁用触控板后强制断电重启,验证 udev 规则文件仍存在且触控板保持禁用
2. 在服务设置宽松 umask(如 0000)下检查
   /etc/udev/rules.d/90-dde-touchpad.rules 权限为 0644
3. 反复启用/禁用触控板,确认 dde-system-daemon 日志无报错

PMS: BUG-374789
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 98 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本次提交加固了 udev 规则文件写入的权限与落盘耐久性,代码逻辑正确,安全考量充分,未发现安全漏洞。存在2个轻微的代码质量改进建议(日志消息准确性、错误处理一致性),不影响代码安全性和功能正确性。

📋 提交信息

项目 内容
PR linuxdeepin/dde-daemon#1223
标题 fix: harden udev rule file permission and durability on write
作者 fly602
修改文件 system/inputdevices1/touchpad.go
分析模式 全量分析

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

得分: 25 / 25

代码语法正确,控制流逻辑清晰。writeUdevRuleFile 函数中 O_EXCL 原子探测首次创建的逻辑处理正确:O_EXCL 成功时 isNew=true;文件已存在时回退到 O_WRONLY 打开;其他错误直接返回。所有错误路径均正确关闭文件句柄后再返回,无资源泄漏。Chmod-before-Truncate 的操作顺序确保了 Chmod 失败时不会因 O_TRUNC 清空原有规则内容。

潜在问题:
✅ 未发现明显问题


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

得分: 23 / 25

函数注释详尽,清楚说明了安全要点(TOCTOU 竞态、权限收窄、断电落盘)。writeUdevRuleFilesyncDirBestEffort 的拆分提升了可读性,行为与原代码一致。commit message 中描述的8项改进均已正确实现。

潜在问题:

  1. 日志消息不准确system/inputdevices1/touchpad.go:228,函数 writeUdevRuleFile
    日志消息 "created udev rule file" 在文件被覆盖更新(非新建)时也会输出,日志信息不准确,可能导致调试时误导。应根据 isNew 标志区分 "created""updated"

  2. 错误处理一致性system/inputdevices1/touchpad.go:223,函数 writeUdevRuleFile
    dir.Close() 返回值未检查。虽然目录 fd 的 Close() 在 Linux 上极少失败,但与函数内其他错误处理模式不一致,建议至少检查并记录错误。

建议:

  1. 使用 isNew 标志区分日志消息:新建时输出 "created",更新时输出 "updated"
  2. 检查 dir.Close() 返回值,与函数内其他错误处理模式保持一致

3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

得分: 20 / 20

快速路径(内容匹配时)仅执行 Chmod + best-effort 目录同步,避免不必要的写入。O_EXCL 单次系统调用即可原子探测首次创建,比 Stat+OpenFile 更高效。fsync 调用对于 udev 规则文件的耐久性保障是必要的,且该操作为低频调用,不影响整体性能。

潜在问题:
✅ 未发现明显问题


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

得分: 30 / 30

本次提交是一次安全加固,通过以下方式提升了 udev 规则文件写入的安全性:

  • os.OpenFile(..., 0644) 替换 os.Create(0666),避免宽松 umask 下权限退化
  • 新增 f.Chmod(0644) 收窄已存在文件的权限
  • 使用 O_EXCL 原子探测首次创建,消除 TOCTOU 竞态
  • 先 Chmod 成功再 Truncate,避免权限修改失败时数据丢失
  • 首次创建后 fsync 父目录,确保目录项断电不丢
  • 快速路径也执行权限收窄,修复已存在但权限被改宽的规则文件

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个

安全漏洞详情:
✅ 未发现安全漏洞


💡 改进建议代码示例

// 建议修复:区分日志消息 + 检查 dir.Close()

// 1. 日志消息区分新建和更新
if isNew {
    logger.Info("created udev rule file:", udevRuleFile)
} else {
    logger.Info("updated udev rule file:", udevRuleFile)
}
return true, nil

// 2. 检查 dir.Close() 返回值
if isNew {
    dir, err := os.Open(filepath.Dir(udevRuleFile))
    if err != nil {
        return false, err
    }
    err = dir.Sync()
    closeErr := dir.Close()
    if err != nil {
        return false, err
    }
    if closeErr != nil {
        logger.Warning("failed to close parent directory after sync:", closeErr)
    }
}

本报告由 AI 代码审查工具自动生成

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@fly602
fly602 merged commit c651c9d into linuxdeepin:master Aug 27, 2026
13 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.

3 participants