Skip to content

fix(bluetooth): prevent path traversal in OBEX file receive#1097

Merged
mhduiy merged 1 commit intolinuxdeepin:masterfrom
mhduiy:sec
May 7, 2026
Merged

fix(bluetooth): prevent path traversal in OBEX file receive#1097
mhduiy merged 1 commit intolinuxdeepin:masterfrom
mhduiy:sec

Conversation

@mhduiy
Copy link
Copy Markdown
Contributor

@mhduiy mhduiy commented May 7, 2026

  1. Add validateFilename() to reject filenames with path separators
  2. Block relative/absolute path traversal attacks (../, /etc/passwd)
  3. Prevent symlink attacks by checking destination before move
  4. Add comprehensive unit tests for filename validation

Log: Fix path traversal vulnerability in Bluetooth OBEX file receive

fix(bluetooth): 修复蓝牙 OBEX 文件接收路径遍历漏洞

  1. 添加 validateFilename() 拒绝包含路径分隔符的文件名
  2. 阻止相对/绝对路径遍历攻击(../、/etc/passwd)
  3. 移动文件前检查目标是否为符号链接,防止符号链接攻击
  4. 添加文件名验证的完整单元测试

Log: 修复蓝牙 OBEX 文件接收路径遍历漏洞

Summary by Sourcery

Harden Bluetooth OBEX file reception against path traversal and symlink-based attacks.

Bug Fixes:

  • Validate incoming OBEX filenames to reject path traversal, absolute paths, and unsafe directory markers.
  • Prevent overwriting of symlink destinations when moving received files.

Tests:

  • Add comprehensive unit tests to cover valid filenames and multiple path traversal and security edge cases for filename validation.

1. Add validateFilename() to reject filenames with path separators
2. Block relative/absolute path traversal attacks (../, /etc/passwd)
3. Prevent symlink attacks by checking destination before move
4. Add comprehensive unit tests for filename validation

Log: Fix path traversal vulnerability in Bluetooth OBEX file receive

fix(bluetooth): 修复蓝牙 OBEX 文件接收路径遍历漏洞

1. 添加 validateFilename() 拒绝包含路径分隔符的文件名
2. 阻止相对/绝对路径遍历攻击(../、/etc/passwd)
3. 移动文件前检查目标是否为符号链接,防止符号链接攻击
4. 添加文件名验证的完整单元测试

Log: 修复蓝牙 OBEX 文件接收路径遍历漏洞
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 7, 2026

Reviewer's Guide

Adds server-side filename validation and symlink protections to the Bluetooth OBEX file receive path to block path traversal and symlink-based attacks, along with unit tests to cover the new validation logic.

Sequence diagram for secure OBEX file receive and move

sequenceDiagram
    actor RemoteDevice
    participant obexAgent
    participant validateFilenameUtil
    participant TempStorage
    participant FileMover
    participant Filesystem

    RemoteDevice->>obexAgent: AuthorizePush(transferPath)
    obexAgent->>TempStorage: Get transfer properties
    TempStorage-->>obexAgent: oriFilename
    obexAgent->>validateFilenameUtil: validateFilename(oriFilename)
    alt invalid filename
        validateFilenameUtil-->>obexAgent: error
        obexAgent-->>RemoteDevice: dbus error invalid filename
    else valid filename
        validateFilenameUtil-->>obexAgent: nil
        obexAgent->>TempStorage: randFileName(oriFilename)
        TempStorage-->>obexAgent: tempFileName
        obexAgent-->>RemoteDevice: tempFileName
        Note over RemoteDevice,obexAgent: file transfer completes
        obexAgent->>FileMover: moveTempFile(srcTempPath, destPath)
        FileMover->>Filesystem: Lstat(destPath)
        alt dest is symlink
            Filesystem-->>FileMover: FileInfo symlink
            FileMover-->>obexAgent: ""
        else dest not symlink
            Filesystem-->>FileMover: FileInfo regular or error
            FileMover->>Filesystem: rename(srcTempPath, destPath)
            Filesystem-->>FileMover: ok
            FileMover-->>obexAgent: destPath
        end
    end
Loading

Class diagram for updated OBEX agent filename validation and file move logic

classDiagram
    class obexAgent {
        +AuthorizePush(transferPath dbus.ObjectPath) string
        +receiveProgress(transfer transferObj)
    }

    class validateFilenameUtil {
        +validateFilename(name string) error
    }

    class FileMover {
        +moveTempFile(src string, dest string) string
    }

    obexAgent ..> validateFilenameUtil : uses
    obexAgent ..> FileMover : uses
Loading

File-Level Changes

Change Details Files
Harden OBEX filename handling by validating incoming filenames and rejecting unsafe values before creating temp files.
  • Introduce validateFilename() helper that rejects empty names, dot/dot-dot, any path separators, and non-pure basenames using filepath.Base checks.
  • Call validateFilename() in AuthorizePush before generating a random temp filename; on failure log the rejected name and return a generic invalid filename error via dbusutil.
  • Enforce a 255-character limit on temp filenames as before, after validation succeeds.
bluetooth1/obex_agent.go
Prevent symlink overwrite when finalizing received files in the OBEX receive path.
  • Update moveTempFile() to Lstat the destination path and short-circuit if it is a symbolic link, logging an error and returning an empty string instead of overwriting.
  • Import os package to support Lstat and symlink detection via file mode bits.
bluetooth1/obex_agent.go
Add unit test coverage for filename validation to ensure both normal and malicious cases are handled correctly.
  • Create obex_agent_test.go with table-driven TestValidateFilename covering normal filenames, path traversal attempts, absolute paths, separator usage, and edge cases like empty or dot/dot-dot names.
  • Add TestValidateFilenameSecurity with focused test cases for typical security-sensitive paths (autostart, ssh, /etc/passwd, Windows-style paths) and acceptance of valid filenames, including those with spaces and Unicode.
bluetooth1/obex_agent_test.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
Copy Markdown

@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 left some high level feedback:

  • The symlink pre-check in moveTempFile introduces a TOCTOU race (an attacker can swap in a symlink between os.Lstat and the subsequent operations); consider using an atomic-safe pattern (e.g., creating/opening the destination with O_EXCL or similar) instead of a separate pre-check.
  • validateFilename currently rejects any name containing / or \ and then also checks filepath.Base(name) != name, which is redundant given the earlier checks; you can simplify by removing the filepath.Base comparison or explain why both are needed for a specific edge case.
  • The two test functions around validateFilename (TestValidateFilename and TestValidateFilenameSecurity) have overlapping coverage; consider consolidating them into a single table-driven test to reduce duplication and make it easier to add/modify cases.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The symlink pre-check in `moveTempFile` introduces a TOCTOU race (an attacker can swap in a symlink between `os.Lstat` and the subsequent operations); consider using an atomic-safe pattern (e.g., creating/opening the destination with `O_EXCL` or similar) instead of a separate pre-check.
- `validateFilename` currently rejects any name containing `/` or `\` and then also checks `filepath.Base(name) != name`, which is redundant given the earlier checks; you can simplify by removing the `filepath.Base` comparison or explain why both are needed for a specific edge case.
- The two test functions around `validateFilename` (`TestValidateFilename` and `TestValidateFilenameSecurity`) have overlapping coverage; consider consolidating them into a single table-driven test to reduce duplication and make it easier to add/modify cases.

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.

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码主要是为了增强蓝牙 OBEX 文件传输的安全性,防止路径遍历攻击和符号链接攻击。以下是对代码的详细审查和改进建议:

1. 语法逻辑审查

优点:

  • validateFilename 函数逻辑清晰,检查了空文件名、特殊目录名(. 和 ..)、路径分隔符,并使用 filepath.Base 确保是纯文件名
  • 测试用例覆盖全面,包括正常情况、路径遍历攻击和边界情况

改进建议:

  1. validateFilename 中,filepath.Base(name) != name 的检查实际上已经被前面的 strings.ContainsAny(name, "/\\") 检查覆盖了,可以考虑移除冗余检查,或者保留作为双重保险
  2. moveTempFile 中,符号链接检查后应该继续检查目标文件是否为目录,防止目录覆盖攻击

2. 代码质量审查

优点:

  • 代码注释详细,特别是安全相关的注释
  • 测试用例组织良好,分为正常情况、攻击场景和边界情况
  • 错误处理得当,使用了明确的错误消息

改进建议:

  1. 可以考虑将 validateFilename 函数移到一个单独的 security.go 文件中,将安全相关的代码集中管理
  2. 测试文件中的 TestValidateFilenameSecurity 函数可以合并到 TestValidateFilename 中,因为测试用例本质上是相同的
  3. moveTempFile 中,符号链接检查后应该继续检查目标文件是否为目录,防止目录覆盖攻击

3. 代码性能审查

优点:

  • 检查操作都是轻量级的,不会对性能产生明显影响

改进建议:

  1. validateFilename 函数中的 strings.ContainsAnyfilepath.Base 调用可以合并为一个检查,减少一次函数调用
  2. moveTempFile 中,os.Lstat 调用后应该立即检查文件是否为目录,避免后续不必要的操作

4. 代码安全审查

优点:

  • 明确防止了路径遍历攻击
  • 防止了符号链接攻击
  • 测试用例覆盖了多种攻击场景

改进建议:

  1. validateFilename 中,应该考虑拒绝包含控制字符的文件名,防止文件名混淆攻击
  2. moveTempFile 中,应该检查目标文件是否为目录,防止目录覆盖攻击
  3. 考虑添加文件名长度限制,防止超长文件名导致的缓冲区溢出或其他问题
  4. AuthorizePush 中,应该考虑对 oriFilename 进行规范化处理,防止 Unicode 规范化攻击

改进后的代码示例

// validateFilename validates that the filename is safe and does not contain
// path traversal sequences or control characters. Only pure filenames are accepted.
//
// Security: Prevents path traversal attacks where a malicious remote device
// sends filenames like "../.config/autostart/evil.desktop" to write files
// outside the intended download directory.
func validateFilename(name string) error {
    // Reject empty filename
    if name == "" {
        return errors.New("empty filename")
    }

    // Reject special directory names
    if name == "." || name == ".." {
        return errors.New("invalid filename: . or ..")
    }

    // Reject path separators - filename must not contain '/' or '\'
    // This prevents absolute paths and relative path traversal
    if strings.ContainsAny(name, "/\\") {
        return errors.New("path separator not allowed in filename")
    }

    // Reject control characters
    for _, r := range name {
        if r < 32 || r == 127 {
            return errors.New("control characters not allowed in filename")
        }
    }

    // Ensure the name is a pure filename, not a path
    // filepath.Base("../foo") returns "foo", which != "../foo"
    if filepath.Base(name) != name {
        return errors.New("not a pure filename")
    }

    // Limit filename length to prevent potential issues
    if len(name) > 255 {
        return errors.New("filename too long")
    }

    return nil
}

func moveTempFile(src, dest string) string {
    // Security: Check if destination is a symlink or directory to prevent attacks.
    // A malicious local user could create a symlink in the download directory
    // pointing to a sensitive file (e.g., ~/.bashrc, ~/.config/autostart/*.desktop).
    // Use os.Lstat to detect symlinks without following them.
    if fi, err := os.Lstat(dest); err == nil {
        if fi.Mode()&os.ModeSymlink != 0 {
            logger.Error("refusing to overwrite symlink:", dest)
            return ""
        }
        if fi.IsDir() {
            logger.Error("refusing to overwrite directory:", dest)
            return ""
        }
    }

    count := 0
    suffix := filepath.Ext(dest)
    fileName := strings.TrimSuffix(dest, suffix)
    // ... rest of the function
}

总结

这段代码在安全性方面做得很好,主要改进点在于:

  1. 添加对控制字符的检查
  2. 添加对目录的检查
  3. 添加文件名长度限制
  4. 考虑代码组织和测试用例的合并

这些改进将使代码更加健壮和安全,同时保持良好的可读性和可维护性。

@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

@mhduiy mhduiy merged commit 7cc8ea8 into linuxdeepin:master May 7, 2026
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.

3 participants