Skip to content

fix(auth): unify caller authentication to isTrustedSender and remove binary path identification#428

Open
Fire-dtx wants to merge 1 commit into
linuxdeepin:masterfrom
Fire-dtx:master
Open

fix(auth): unify caller authentication to isTrustedSender and remove binary path identification#428
Fire-dtx wants to merge 1 commit into
linuxdeepin:masterfrom
Fire-dtx:master

Conversation

@Fire-dtx
Copy link
Copy Markdown
Contributor

Replace binary path-based caller identification (getExecutablePathAndCmdline, mapMethodCaller, checkInvokePermission) with isTrustedSender + polkit authentication for DistUpgradePartly and PrepareDistUpgradePartly interfaces. Remove caller authentication from RemovePackage interface. Add appstore_intranet.list to trusted source list. Remove deprecated deny-exec-whitelist and install-package-support-auth config items.

Introduce manager_auth.go with allow-caller registration, lightdm trusted UID support, and persistent runtime state under /run/lastore. Export SetAllowCaller D-Bus method for deepin-security-loader integration. Add D-Bus access rules for SetAllowCaller deny and deepin-daemon group policy. Configure RuntimeDirectory with 0700 mode and preserve semantics.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Fire-dtx

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

Comment thread lib/systemd/system/lastore-daemon.service
Comment thread src/lastore-daemon/common.go Outdated
@Fire-dtx Fire-dtx force-pushed the master branch 6 times, most recently from e4fe428 to 617487e Compare May 26, 2026 07:43
…d polkit

Replace binary path-based caller identification with isTrustedSender +
polkit authentication across all protected D-Bus interfaces. Add allow-caller
registration mechanism for deepin-security-loader integration.

Remove binary path constants and whitelists:
- getExecutablePathAndCmdline, mapMethodCaller, checkSenderNsMntValid
- allowInstallPackageExecPaths, allowRemovePackageExecPaths

Add isTrustedSender check with polkit fallback in checkInvokePermission
(Manager method) for InstallPackage, RemovePackage, DistUpgradePartly,
PrepareDistUpgradePartly, PrepareFullScreenUpgrade, SetUpdateSources,
UpdateMode, CheckUpdateModeWrite, and SetMirrorSource.

Add manager_auth.go:
- Allow-caller registration with persistent runtime state under /run/lastore
- Lightdm trusted UID support via initTrustedCallerUIDs
- Export SetAllowCaller D-Bus method

D-Bus access rules: deny SetAllowCaller/PowerOff for default policy,
allow deepin-daemon and lightdm groups.

systemd: RuntimeDirectoryMode 0700 with RuntimeDirectoryPreserve=yes.

Refactor PrepareFullScreenUpgrade to use terminate() closure, removing
dead lastore-upgrader.service fallback path. Remove inline PowerOff
execPath check. Add isInstallLikeJobType helper. Remove deprecated
install-package-support-auth config item.

Add unit tests for manager_auth and isInstallLikeJobType.
Fix appinfo_test to use t.TempDir().
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这是一个非常全面且核心的权限重构补丁。该补丁将 lastore-daemon 的鉴权模型从基于可执行文件路径的白名单机制迁移到了基于 DBus Unique Name 动态注册 + 可信 UID + Polkit 的混合鉴权模型

整体来看,这个重构方向是正确的,解决了旧模型中路径伪造、更新后路径失效 以及硬编码等一系列问题。但代码在逻辑严谨性、性能和安全性上仍有改进空间。

以下是详细的审查意见:

一、 安全性

1. SetAllowCaller 存在越权/劫持风险 (高危)
addAllowCaller 中,仅检查了 owner == uniqueName,这意味着任何拥有该 Unique Name 的进程都能将自己注册为 AllowCaller,从而绕过后续的 Polkit 鉴权。

  • 风险:恶意进程可以通过 DBus 获取自己的 Unique Name 并调用 SetAllowCaller,直接将自己加入免鉴权白名单。
  • 改进建议SetAllowCaller 本身必须受到严格的权限控制。虽然 DBus 配置文件中 deny 了默认用户调用,但应当确保只有受信任的 Daemon(如 root 或 deepin-daemon 组)才能代为注册。建议在 SetAllowCaller 入口处增加调用者(sender)的身份校验,确保是受信任的 Loader 在调用:
    func (m *Manager) SetAllowCaller(sender dbus.Sender, uniqueName string) *dbus.Error {
        // 校验调用 SetAllowCaller 的发起者是否是 root 或 deepin-daemon 组
        uid, err := m.service.GetConnUID(string(sender))
        if err != nil || uid != 0 {
            // 或者校验 sender 是否属于 deepin-daemon 组
            return dbusutil.ToError(fmt.Errorf("permission denied for sender %v", sender))
        }
        // ... 原有逻辑
    }
    (注意:当前 exported_methods_auto.goSetAllowCallerInArgs 缺少 sender,需补充以获取调用方身份,如:InArgs: []string{"sender", "uniqueName"})

2. allowCallerStateFile 权限与竞态 (中危)

  • RuntimeDirectoryMode=0700 保证了 /run/lastore/ 目录属于 root,但 persistAllowCallerStateos.MkdirAll(filepath.Dir(allowCallerStateFile), 0700) 如果在目录已存在且权限不严格时不会修改权限。
  • 改进建议:在写入文件前,确保文件权限绝对安全,建议使用 os.WriteFile 替代 kf.SaveToFile(如果底层无法控制权限),或者在创建后显式 os.Chmod
    err = kf.SaveToFile(allowCallerStateFile)
    if err == nil {
        // 确保文件权限为 0600,防止其他用户窥探或篡改白名单
        os.Chmod(allowCallerStateFile, 0600)
    }

3. PowerOff 方法鉴权被移除 (中危)
旧代码中 PowerOff 限制了只有 dde-updatedde-rollback 才能调用,新代码直接移除了所有鉴权。

  • 风险:任何能连上 System Bus 的用户都可以直接调用 PowerOff 让机器关机/重启。
  • 改进建议:必须补充 m.checkInvokePermission(sender) 或恢复原有的路径校验逻辑。

二、 逻辑与语法

1. loadAllowCaller 中的锁粒度问题与潜在死锁
loadAllowCaller 在持有 allowCallerStateMu 的情况下调用了 m.getSystemBusID(),这是一个 DBus IPC 调用。在持锁期间进行 IPC 极易导致死锁或长时间阻塞。

  • 改进建议:将 DBus 调用移到锁外部:
    func (m *Manager) loadAllowCaller() {
        currentBusID, err := m.getSystemBusID() // 先获取 BusID
        if err != nil {
            logger.Warning(err)
            return
        }
    
        allowCallerStateMu.Lock()
        state, err := readAllowCallerState(allowCallerStateFile)
        // ... 读取状态
        callersToCheck := append(strv.Strv(nil), state.Callers...)
        allowCallerStateMu.Unlock() // 尽早释放锁
        // ... 后续 IPC 校验
    }

2. isInstallLikeJobType 未被使用
job_manager.go 中新增了 isInstallLikeJobType 函数,但在整个 diff 中没有任何地方调用它。

  • 改进建议:如果是遗留代码,请删除以保持代码整洁;如果是为后续准备的,建议在注释中说明 TODO: use this function in ...

3. Terminate 逻辑重构后的行为变化
PrepareFullScreenUpgrade 中将 for 循环改为了 terminate() 函数。旧逻辑中,只要任意一步出错就会 break 然后重启 display-manager;新逻辑中,如果 Terminate 成功,会返回 nil,如果失败,会返回 err

  • 问题:新逻辑中,如果 seat.Terminate(0) 成功,返回 nil,外层不会重启 display-manager(这是正确的)。但如果获取 session 失败,返回 err,外层会重启 display-manager(这也是对的)。但 terminate() 函数内的 logger.Warning(err) 会导致错误被打印两次(内部一次,外层一次)。
  • 改进建议:去掉 terminate() 内部的 logger.Warning,统一由外层处理日志。

三、 性能

1. isTrustedSender 中的高频锁竞争
isTrustedSender 在每次 DBus 方法调用时都会被调用(如 InstallPackage, RemovePackage 等),其内部使用了 m.PropsMu.RLock()。如果并发请求较多,这会成为性能瓶颈。

  • 改进建议allowCallServiceList 是一个相对低频更新的白名单,高频读取时使用读写锁是合理的,但更好的方式是使用原子替换(Snapshot)来做到无锁读取:
    // 使用不可变切片,更新时整体替换指针
    var allowCallersSnapshot strv.Strv
    
    func (m *Manager) isTrustedSender(uid uint32, sender dbus.Sender) bool {
        // ...
        current := m.allowCallersSnapshot // 原子读取指针
        return current.Contains(string(sender))
    }

四、 代码质量

1. DBus 配置文件中的 deny 规则顺序问题
org.deepin.dde.Lastore1.conf 中:

<policy context="default">
    <allow send_destination="org.deepin.dde.Lastore1"/>
    <deny send_destination="org.deepin.dde.Lastore1" send_member="SetAllowCaller"/>
</policy>

DBus 配置的匹配规则是后匹配优先,且具体规则优先级高于通用规则。这里先 allow 了整个 destination,然后又 deny 了特定 member,这在某些 dbus-daemon 实现中可能会因为第一句通用 allow 而导致 deny 失效。

  • 改进建议:最安全的做法是,在 default 上下文中默认 deny,然后在特定 group/user 上下文中 allow
    <policy context="default">
        <allow send_destination="org.deepin.dde.Lastore1"/>
        <!-- 显式拒绝高危接口 -->
        <deny send_destination="org.deepin.dde.Lastore1" send_interface="org.deepin.dde.Lastore1" send_member="SetAllowCaller"/>
        <deny send_destination="org.deepin.dde.Lastore1" send_interface="org.deepin.dde.Lastore1" send_member="PowerOff"/>
    </policy>

2. 测试用例 TestAppInfo 的修复
补丁中将 /tmp/appinfo.json 改为了 t.TempDir(),这是一个非常好的改进,符合测试用例不依赖全局路径的最佳实践。

3. checkSenderNsMntValid 的移除
移除命名空间校验是合理的,因为基于 PID 的校验在 PID 回收时存在竞态条件,且新的 Unique Name 注册机制已经从 DBus Daemon 层面保证了连接的真实性。

总结

此次重构大幅提升了鉴权的安全性和可维护性,尤其是引入了 DBus Unique Name 动态注册机制,非常值得肯定。但必须修复 SetAllowCaller 缺少调用方身份校验的漏洞,以及恢复 PowerOff 的鉴权逻辑,否则会导致严重的安全退化。同时建议优化 loadAllowCaller 中的锁范围,避免潜在的死锁问题。

@deepin-bot
Copy link
Copy Markdown
Contributor

deepin-bot Bot commented Jun 3, 2026

TAG Bot

New tag: 6.2.59
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #432

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