Skip to content

fix: prevent password leakage in ModifyPasswd#1044

Merged
ComixHe merged 1 commit intolinuxdeepin:masterfrom
ComixHe:passwd
Mar 4, 2026
Merged

fix: prevent password leakage in ModifyPasswd#1044
ComixHe merged 1 commit intolinuxdeepin:masterfrom
ComixHe:passwd

Conversation

@ComixHe
Copy link
Contributor

@ComixHe ComixHe commented Mar 4, 2026

Replace usermod -p with chpasswd -e and pass password via stdin instead of command line arguments to avoid exposing passwords in process listings.

Summary by Sourcery

Bug Fixes:

  • Prevent password values from being visible in process listings by sending them to chpasswd via stdin instead of passing them as usermod arguments.

@ComixHe ComixHe requested a review from fly602 March 4, 2026 07:24
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 4, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

ModifyPasswd no longer invokes usermod -p with the password as a command-line argument; it now uses chpasswd -e and passes the password via stdin while capturing stderr for better error reporting, with a new constant added for the chpasswd command name.

Sequence diagram for ModifyPasswd using chpasswd with stdin

sequenceDiagram
    actor Admin
    participant UsersPackage as users_package
    participant Exec as os_exec
    participant Chpasswd as chpasswd_process

    Admin->>UsersPackage: ModifyPasswd(words, username)
    UsersPackage->>UsersPackage: validate words
    alt invalid_parameters
        UsersPackage-->>Admin: errInvalidParam
    else valid_parameters
        UsersPackage->>Exec: Command(pwdCmdModify, "-e")
        Exec-->>UsersPackage: *exec.Cmd
        UsersPackage->>UsersPackage: format input "username:words\n"
        UsersPackage->>Chpasswd: set Stdin to input buffer
        UsersPackage->>Chpasswd: set Stderr to stderr buffer
        UsersPackage->>Chpasswd: Run()
        alt chpasswd_error
            Chpasswd-->>UsersPackage: error
            UsersPackage-->>Admin: fmt.Errorf("failed to modify password: ...")
        else chpasswd_success
            Chpasswd-->>UsersPackage: nil
            UsersPackage-->>Admin: nil
        end
    end
Loading

Class diagram for updated users package password modification

classDiagram
    class UsersPackage {
        <<package>>
    }

    class Constants {
        <<constant_group>>
        string userCmdAdd
        string userCmdDelete
        string userCmdModify
        string userCmdGroup
        string pwdCmdModify
        string cmdGroupDel
        string cmdChAge
    }

    class Functions {
        error ModifyPasswd(words string, username string)
        error ModifyMaxPasswordAge(username string, nDays int)
    }

    UsersPackage --> Constants
    UsersPackage --> Functions

    class ModifyPasswdFlow {
        - validateParameters(words string, username string) error
        - runChpasswd(username string, words string) error
    }

    Functions --> ModifyPasswdFlow
    ModifyPasswdFlow ..> pwdCmdModify : uses
    ModifyPasswdFlow ..> execCmd : uses

    class execCmd {
        <<external>>
        +Command(name string, arg1 string) *Cmd
        +Run() error
        +Stdin ioReader
        +Stderr ioWriter
    }

    class ioReader {
        <<interface>>
    }

    class ioWriter {
        <<interface>>
    }

    ModifyPasswdFlow ..> ioReader
    ModifyPasswdFlow ..> ioWriter
Loading

File-Level Changes

Change Details Files
Change ModifyPasswd to use chpasswd with stdin instead of usermod -p to avoid password exposure and improve error reporting.
  • Replace the call to the generic doAction wrapper with a direct exec.Command invocation using the chpasswd binary and the -e flag to indicate an already-encrypted password.
  • Construct the chpasswd input payload as username:password and feed it via a bytes buffer connected to the command's standard input instead of command-line arguments.
  • Capture stderr output from the chpasswd command into a buffer and include it in the returned error message when the command fails.
  • Return nil explicitly after successful command execution.
accounts1/users/prop.go
Introduce a dedicated constant for the password modification command used by ModifyPasswd.
  • Add a new constant pwdCmdModify set to chpasswd alongside the existing user and group management command constants.
  • Use the new pwdCmdModify constant in the ModifyPasswd implementation to decouple password changes from the usermod command.
accounts1/users/manager.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

Copy link

@sourcery-ai sourcery-ai bot left a comment

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 security issue, 1 other issue, and left some high level feedback:

Security issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)

General comments:

  • Consider reusing the existing doAction helper (or extending it) rather than invoking exec.Command directly here, so that process execution and error handling remain consistent with the rest of the user management commands.
  • When wrapping the error from cmd.Run, use %w in fmt.Errorf (e.g. fmt.Errorf("failed to modify password: %w, %s", err, stderr.String())) so callers can unwrap and inspect the underlying error if needed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider reusing the existing `doAction` helper (or extending it) rather than invoking `exec.Command` directly here, so that process execution and error handling remain consistent with the rest of the user management commands.
- When wrapping the error from `cmd.Run`, use `%w` in `fmt.Errorf` (e.g. `fmt.Errorf("failed to modify password: %w, %s", err, stderr.String())`) so callers can unwrap and inspect the underlying error if needed.

## Individual Comments

### Comment 1
<location path="accounts1/users/prop.go" line_range="190" />
<code_context>
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("failed to modify password: %v, %s", err, stderr.String())
</code_context>
<issue_to_address>
**suggestion:** Use error wrapping and consider formatting stderr conditionally to improve error reporting.

Currently the error is built with `fmt.Errorf("failed to modify password: %v, %s", err, stderr.String())`. Two suggestions:

1) Use `%w` so callers can unwrap the original error: `fmt.Errorf("failed to modify password: %w", err)`.
2) Include stderr only when non-empty, and/or with a clear prefix (e.g. `"; stderr=%s"`) to avoid odd messages (like trailing comma/space) while keeping useful diagnostics.

Suggested implementation:

```golang
	err := cmd.Run()
	if err != nil {
		msg := "failed to modify password"
		if s := strings.TrimSpace(stderr.String()); s != "" {
			msg = fmt.Sprintf("%s; stderr=%s", msg, s)
		}
		return fmt.Errorf("%s: %w", msg, err)
	}

```

You will also need to ensure that the file imports the `strings` package:

1. Add `strings` to the import block, e.g. `import ("bytes"; "fmt"; "os/exec"; "strings")` or similar, respecting the existing import style and order.
</issue_to_address>

### Comment 2
<location path="accounts1/users/prop.go" line_range="183" />
<code_context>
	cmd := exec.Command(pwdCmdModify, "-e")
</code_context>
<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</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.

Replace usermod -p with chpasswd -e and pass password via stdin instead of
command line arguments to avoid exposing passwords in process listings.

Signed-off-by: ComixHe <heyuming@deepin.org>
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码主要修改了 ModifyPasswd 函数的实现方式,从使用 usermod 命令改为使用 chpasswd 命令来修改用户密码。以下是对该 diff 的详细审查意见,分为语法逻辑、代码质量、代码性能和代码安全四个方面:

1. 语法逻辑

  • 基本正确:代码逻辑是通顺的。它构建了一个 exec.Command,通过标准输入(Stdin)将 username:password\n 格式的字符串传递给 chpasswd 命令,并捕获了标准错误输出用于错误处理。
  • 潜在问题(参数)
    • chpasswd 命令通常不需要 -e 参数。-e 参数通常用于表示输入的密码是加密过的。如果 words 参数传入的是明文密码(根据函数名 ModifyPasswd 和上下文推测),加上 -e 会导致密码修改失败,因为系统会尝试将明文当作加密密文去验证。
    • 建议:如果传入的是明文密码,应移除 -e 参数;如果需要加密算法控制(如 SHA512),通常使用 -c 参数(如 -c SHA512)。请确认 words 的格式。

2. 代码质量

  • 错误处理:代码增加了对 stderr 的捕获,并在错误发生时返回了包含具体错误信息的 fmt.Errorf。这比原来的 doAction(假设它只返回简单的错误)要好,有助于调试。
  • 常量定义:在 manager.go 中添加了 pwdCmdModify 常量,符合 Go 语言的命名规范和常量管理习惯,值得肯定。
  • 硬编码chpasswd 命令的行为(如加密算法)通常依赖于系统的 PAM 配置。如果代码对安全性有特定要求,建议显式指定加密方式(例如通过 -c SHA512),而不是依赖系统默认配置,以保证跨环境的一致性。

3. 代码性能

  • 开销exec.Command 会创建一个新的进程,这本身有一定的性能开销。但在修改密码这种低频操作中,这是可以接受的。
  • 内存分配bytes.NewBufferString 会进行内存分配。对于极高频的调用场景(虽然密码修改不属于此类),可以考虑使用 strings.NewReader,但在当前场景下性能差异可忽略不计。

4. 代码安全 —— 非常重要

  • 命令注入风险
    • 虽然使用了 exec.Command 而非 exec.Command("sh", "-c", ...),有效防止了 Shell 注入,但在构建 input 字符串时,直接将 usernamewords 拼接进去。虽然 chpasswd 通常按行解析,对特殊字符处理较好,但如果 usernamewords 包含换行符(\n),可能会导致命令解析异常或注入额外的指令。
    • 建议:应对 username 进行严格的校验(例如只允许字母、数字、下划线、短横线),防止恶意输入。
  • 密码泄露风险
    • 进程参数:原来的代码 usermod -p ... 会将密码(即使是加密后的)作为命令行参数传递,这意味着密码可能会在系统的进程列表(如 ps 命令)中可见。新的代码通过 Stdin 传递密码,避免了密码出现在命令行参数中,这是一个显著的安全改进。
    • 环境变量:代码中没有通过环境变量传递密码,这是正确的。
    • 日志/调试:确保 stderr 的内容(以及可能的日志系统)不会意外记录包含密码的输入。目前的代码只记录了 stderr,通常不包含输入,但需注意。
  • 加密算法
    • chpasswd 默认使用的加密算法取决于系统的 PAM 配置(通常是 DES、MD5 或 SHA256/512)。为了安全起见,建议显式指定使用强加密算法(如 SHA512),即添加参数 -c SHA512

改进建议代码示例

假设 words 是明文密码,并且需要显式指定 SHA512 加密,同时增加简单的输入校验:

import (
	"bytes"
	"fmt"
	"os/exec"
	"regexp" // 用于用户名校验
	"strings"
)

// ... (常量定义保持不变)

func ModifyPasswd(words, username string) error {
	// 1. 参数校验
	if words == "" || username == "" {
		return errInvalidParam
	}

	// 2. 用户名格式校验,防止注入或非法字符
	// 常见的 Linux 用户名规则:小写字母、数字、下划线、短横线,不以短横线开头
	matched, _ := regexp.MatchString(`^[a-z_][a-z0-9_-]*$`, username)
	if !matched {
		return fmt.Errorf("invalid username format: %s", username)
	}

	// 3. 构建命令
	// 注意:移除了 -e,假设 words 是明文。
	// 如果需要指定加密算法,可以添加参数,例如:[]string{"-c", "SHA512"}
	cmd := exec.Command(pwdCmdModify) 
	
	// 4. 准备输入
	// 使用 strings.NewReader 替代 bytes.NewBufferString,语义更清晰(只读)
	input := fmt.Sprintf("%s:%s", username, words)
	cmd.Stdin = strings.NewReader(input)

	// 5. 捕获输出
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	// 6. 执行
	err := cmd.Run()
	if err != nil {
		// 返回详细的错误信息,注意不要泄露 words
		return fmt.Errorf("failed to modify password for user %s: %v, %s", username, err, stderr.String())
	}

	return nil
}

总结

这段代码在安全性上相比直接使用 usermod -p 有所提升(避免了命令行参数泄露),但在 chpasswd 的参数使用上(-e)可能存在逻辑错误。建议确认密码格式(明文还是密文),移除或调整参数,并增加对用户名的格式校验以提高健壮性。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@ComixHe ComixHe merged commit 6225167 into linuxdeepin:master Mar 4, 2026
12 of 15 checks passed
@ComixHe ComixHe deleted the passwd branch March 4, 2026 07:44
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