Skip to content

fix: improve socket security and refactor hook execution#1673

Closed
dengbo11 wants to merge 0 commit into
OpenAtom-Linyaps:masterfrom
dengbo11:fix-security
Closed

fix: improve socket security and refactor hook execution#1673
dengbo11 wants to merge 0 commit into
OpenAtom-Linyaps:masterfrom
dengbo11:fix-security

Conversation

@dengbo11

Copy link
Copy Markdown
Collaborator
  1. Move package manager socket to dedicated directory /tmp/linglong/
  2. Create socket directory with secure permissions (rwxr-xr-x)
  3. Set socket file permissions to 0600 (owner read/write only)
  4. Clean up stale socket files from previous crashes on startup
  5. Improve error logging to include socket path in failure messages
  6. Refactor hook command execution to use Cmd class instead of std::system()
  7. Remove custom shell escaping logic in favor of Cmd::exec() method

Influence:

  1. Test package manager daemon startup in no-dbus mode
  2. Verify socket directory creation with correct permissions
  3. Verify socket file has 0600 permissions after creation
  4. Test daemon restart after crash to verify stale socket cleanup
  5. Test package installation with pre/post install hooks
  6. Verify hook commands execute correctly with environment variables
  7. Test hook execution failure handling and error reporting
  8. Verify socket cleanup on normal daemon shutdown

@dengbo11 dengbo11 requested review from ComixHe and reddevillg June 11, 2026 12:27

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves security by moving the package manager socket to a dedicated /tmp/linglong directory with restricted permissions and refactors hook command execution to use a Cmd wrapper instead of std::system. The review feedback identifies a critical security vulnerability regarding insecure directory creation in /tmp (susceptible to TOCTOU/symlink attacks) and recommends verifying directory ownership and type using lstat and getuid(), as well as including <unistd.h>.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/ll-package-manager/src/main.cpp Outdated
Comment on lines +172 to +188
if (!std::filesystem::exists(socketDir, ec)) {
std::filesystem::create_directory(socketDir, ec);
if (ec) {
LogE("Failed to create socket directory /tmp/linglong: {}", ec.message());
return -1;
}
std::filesystem::permissions(
socketDir,
std::filesystem::perms::owner_all | std::filesystem::perms::group_read
| std::filesystem::perms::group_exec | std::filesystem::perms::others_read
| std::filesystem::perms::others_exec,
ec);
if (ec) {
LogE("Failed to set permissions on /tmp/linglong: {}", ec.message());
return -1;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Creating a directory in a world-writable location like /tmp without verifying its ownership and type introduces a critical security vulnerability (symlink/TOCTOU attack). If an attacker pre-creates /tmp/linglong with loose permissions (e.g., 0777), they can delete or replace the socket file inside it because directory write permissions allow file deletion/renaming.

To prevent this, you should:

  1. Use lstat to verify that if /tmp/linglong already exists, it is a directory (not a symlink) and is owned by the current user (getuid()).
  2. Set the directory permissions to 0700 (owner-all) to prevent other users from accessing or manipulating the directory contents.
        struct stat st;
        if (lstat(socketDir.c_str(), &st) == 0) {
            if (!S_ISDIR(st.st_mode)) {
                LogE("Path /tmp/linglong exists but is not a directory");
                return -1;
            }
            if (st.st_uid != getuid()) {
                LogE("Directory /tmp/linglong is owned by UID {}, expected {}", st.st_uid, getuid());
                return -1;
            }
            // Ensure secure permissions (0700) on the existing directory
            if (chmod(socketDir.c_str(), S_IRWXU) != 0) {
                LogE("Failed to set secure permissions on /tmp/linglong: {}", strerror(errno));
                return -1;
            }
        } else {
            if (errno != ENOENT) {
                LogE("Failed to stat /tmp/linglong: {}", strerror(errno));
                return -1;
            }
            std::filesystem::create_directory(socketDir, ec);
            if (ec) {
                LogE("Failed to create socket directory /tmp/linglong: {}", ec.message());
                return -1;
            }
            std::filesystem::permissions(socketDir, std::filesystem::perms::owner_all, ec);
            if (ec) {
                LogE("Failed to set permissions on /tmp/linglong: {}", ec.message());
                return -1;
            }
        }

Comment thread apps/ll-package-manager/src/main.cpp Outdated
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 44 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
apps/ll-package-manager/src/main.cpp 0.00% 35 Missing ⚠️
libs/utils/src/linglong/utils/hooks.cpp 0.00% 8 Missing ⚠️
libs/linglong/src/linglong/cli/cli.cpp 0.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
libs/linglong/src/linglong/cli/cli.cpp 1.75% <0.00%> (ø)
libs/utils/src/linglong/utils/hooks.cpp 0.00% <0.00%> (ø)
apps/ll-package-manager/src/main.cpp 0.00% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@deepin-ci-robot

Copy link
Copy Markdown
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dengbo11

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

@dengbo11

Copy link
Copy Markdown
Collaborator Author

/gemini review

@dengbo11 dengbo11 requested a review from deepin-ci-robot June 11, 2026 13:28

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances security by creating a dedicated, restricted directory (/tmp/linglong) for the package manager's UNIX socket and setting strict permissions on both the directory and the socket file. It also refactors hook command execution to use the Cmd utility instead of std::system. A security review identified a TOCTOU vulnerability and insecure shared directory ownership when creating /tmp/linglong, recommending using mkdir atomically and verifying directory ownership.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/ll-package-manager/src/main.cpp Outdated
@dengbo11 dengbo11 force-pushed the fix-security branch 2 times, most recently from ba7b6a8 to bd8ad4b Compare June 11, 2026 14:01
@dengbo11

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves security by creating a dedicated, restricted socket directory under /tmp/linglong and replacing manual shell command escaping and std::system calls with a safer Cmd wrapper. The review feedback identifies a potential Time-of-Check to Time-of-Use (TOCTOU) race condition during the directory's creation and permission checks, recommending the use of file descriptors (open, fstat, and fchmod) to perform these operations safely, as well as including the <fcntl.h> header.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/ll-package-manager/src/main.cpp Outdated
Comment on lines +177 to +207
if (mkdir(socketDir.c_str(), S_IRWXU) == 0) {
} else if (errno == EEXIST) {
struct stat st;
if (lstat(socketDir.c_str(), &st) != 0) {
LogE("Failed to stat /tmp/linglong: {}", strerror(errno));
return -1;
}
if (S_ISLNK(st.st_mode)) {
LogE("Failed to create /tmp/linglong: it is a symbolic link. Please remove it.");
return -1;
}
if (!S_ISDIR(st.st_mode)) {
LogE("Failed to create /tmp/linglong: it exists but is not a directory. Please "
"remove it.");
return -1;
}
if (st.st_uid != getuid()) {
LogE("Failed to create /tmp/linglong: owner mismatch (expected uid {}, got {}). "
"Please remove it.",
getuid(),
st.st_uid);
return -1;
}
if (chmod(socketDir.c_str(), S_IRWXU) != 0) {
LogE("Failed to set permissions on /tmp/linglong: {}", strerror(errno));
return -1;
}
} else {
LogE("Failed to create /tmp/linglong: {}", strerror(errno));
return -1;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The directory check and permission setting logic has a Time-of-Check to Time-of-Use (TOCTOU) race condition. If an attacker replaces /tmp/linglong with a symbolic link after the lstat check but before the chmod call, chmod will follow the symbolic link and modify the permissions of the target file/directory (potentially a sensitive system file). To prevent this, open the directory safely using open with O_DIRECTORY | O_NOFOLLOW to obtain a file descriptor, and then perform fstat and fchmod on that descriptor. This also eliminates the empty if block style issue.

        if (mkdir(socketDir.c_str(), S_IRWXU) != 0) {
            if (errno == EEXIST) {
                int fd = open(socketDir.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
                if (fd < 0) {
                    LogE("Failed to open /tmp/linglong safely: {}", strerror(errno));
                    return -1;
                }
                struct stat st;
                if (fstat(fd, &st) != 0) {
                    LogE("Failed to fstat /tmp/linglong: {}", strerror(errno));
                    close(fd);
                    return -1;
                }
                if (st.st_uid != getuid()) {
                    LogE("Failed to create /tmp/linglong: owner mismatch (expected uid {}, got {}).",
                         getuid(),
                         st.st_uid);
                    close(fd);
                    return -1;
                }
                if (fchmod(fd, S_IRWXU) != 0) {
                    LogE("Failed to set permissions on /tmp/linglong: {}", strerror(errno));
                    close(fd);
                    return -1;
                }
                close(fd);
            } else {
                LogE("Failed to create /tmp/linglong: {}", strerror(errno));
                return -1;
            }
        }

Comment thread apps/ll-package-manager/src/main.cpp Outdated
@dengbo11

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves security and robustness by moving the package manager's Unix socket to a dedicated /tmp/linglong directory with strict ownership and permission checks, and by replacing the use of std::system with a safer Cmd wrapper for executing hook commands. A review comment identifies a potential file descriptor leak in the socket directory setup where fd is not guarded by finally immediately after being opened, which could lead to leaks if subsequent checks fail.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/ll-package-manager/src/main.cpp Outdated
Comment on lines +177 to +201
int fd = open(socketDir.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) {
LogE("Failed to open /tmp/linglong safely: {}", strerror(errno));
return -1;
}
struct stat st;
if (fstat(fd, &st) != 0) {
LogE("Failed to fstat /tmp/linglong: {}", strerror(errno));
return -1;
}
if (st.st_uid != getuid()) {
LogE(
"Failed to create /tmp/linglong: owner mismatch (expected uid {}, got {}).",
getuid(),
st.st_uid);
return -1;
}
if (fchmod(fd, S_IRWXU) != 0) {
LogE("Failed to set permissions on /tmp/linglong: {}", strerror(errno));
return -1;
}

auto close_fd = linglong::utils::finally::finally([fd] {
close(fd);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The file descriptor fd is leaked if any of the subsequent checks (fstat, st_uid check, or fchmod) fail and return -1 before the finally guard close_fd is constructed. Moving the construction of close_fd immediately after the successful open call ensures that the file descriptor is always safely closed on any exit path.

                int fd = open(socketDir.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
                if (fd < 0) {
                    LogE("Failed to open /tmp/linglong safely: {}", strerror(errno));
                    return -1;
                }
                auto close_fd = linglong::utils::finally::finally([fd] {
                    close(fd);
                });
                struct stat st;
                if (fstat(fd, &st) != 0) {
                    LogE("Failed to fstat /tmp/linglong: {}", strerror(errno));
                    return -1;
                }
                if (st.st_uid != getuid()) {
                    LogE(
                      "Failed to create /tmp/linglong: owner mismatch (expected uid {}, got {}).",
                      getuid(),
                      st.st_uid);
                    return -1;
                }
                if (fchmod(fd, S_IRWXU) != 0) {
                    LogE("Failed to set permissions on /tmp/linglong: {}", strerror(errno));
                    return -1;
                }

@dengbo11

Copy link
Copy Markdown
Collaborator Author

/gemini review

@deepin-ci-robot

Copy link
Copy Markdown
Collaborator

deepin pr auto review

代码审查报告

总体评价

这段代码主要实现了 linglong 包管理器的核心功能,包括守护进程模式、D-Bus 通信和钩子执行等功能。代码整体质量较高,但有一些可以改进的地方。

主要改进建议

1. 安全性增强

main.cpp 改进

  • 在创建 /tmp/linglong 目录时,已经进行了权限检查,但建议使用 mkstemp 或类似方法创建临时目录,以避免 TOCTOU (Time-of-Check to Time-of-Use) 问题
  • D-Bus socket 路径现在使用了专门的目录,这是好的改进,但建议使用更安全的临时目录创建方法,如 mkdtemp
  • 在设置 socket 权限时使用了 S_IRUSR | S_IWUSR,这是正确的,但可以考虑添加 S_IRGRP | S_IWGRP 以支持组访问

hooks.cpp 改进

  • 移除了 escapeAndWrapCommandForShell 函数是正确的,使用 Cmd 类来执行命令更安全
  • 建议为 Cmd 类添加命令参数验证,防止命令注入

2. 错误处理改进

main.cpp 改进

  • 在检查目录权限时,建议添加更多的错误检查,如检查目录是否确实是一个目录
  • 在删除 socket 文件时,建议检查删除操作是否真的成功

hooks.cpp 改进

  • 在执行钩子命令时,建议添加超时机制,防止命令无限期运行
  • 建议记录每个钩子命令的执行结果,便于调试

3. 代码结构改进

main.cpp 改进

  • 将 socket 创建和权限设置逻辑提取为单独的函数,提高代码可读性
  • 守护进程模式的逻辑可以进一步模块化

hooks.cpp 改进

  • 钩子命令的执行逻辑可以进一步抽象,便于扩展
  • 环境变量设置可以集中管理

4. 性能优化

main.cpp 改进

  • 在检查目录权限时,使用了 fstat,这是正确的,但可以考虑使用 faccessat 来检查权限,这可能更高效
  • 在删除 socket 文件时,使用了 std::filesystem::remove,这是正确的,但可以考虑使用 unlink 直接删除,可能更高效

hooks.cpp 改进

  • 钩子命令的执行是串行的,可以考虑并行执行一些独立的钩子命令,提高性能

5. 日志记录改进

main.cpp 改进

  • 建议添加更详细的日志记录,特别是在关键操作前后
  • 在错误情况下,建议记录更多上下文信息

hooks.cpp 改进

  • 建议为每个钩子命令的执行添加更详细的日志记录
  • 建议记录环境变量的设置情况

具体代码建议

  1. 在 main.cpp 中,建议将 socket 创建逻辑提取为单独的函数:
auto createAndSecureSocketPath(const std::filesystem::path &socketDir) -> std::optional<std::filesystem::path>
{
    // 实现细节...
}
  1. 在 hooks.cpp 中,建议为钩子命令添加超时机制:
auto executeHookWithTimeout(const std::string &command, 
                          const std::vector<std::pair<std::string, std::string>> &envVars,
                          std::chrono::seconds timeout) -> utils::error::Result<void>
{
    // 实现细节...
}
  1. 建议为所有关键操作添加单元测试,特别是与安全相关的操作。

总结

代码整体质量良好,安全性考虑较为周全。主要的改进空间在于代码结构的模块化、错误处理的完善和日志记录的增强。建议在后续开发中逐步实施这些改进,以提高代码的可维护性和可靠性。

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves security and robustness by moving the package manager's UNIX domain socket to a dedicated, securely-created directory (/tmp/linglong) and refactoring hook command execution to use a helper class instead of std::system. A security review comment correctly identifies a race condition between socket creation and the subsequent chmod call, recommending the use of umask to securely initialize the socket file permissions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/ll-package-manager/src/main.cpp Outdated
Comment on lines +217 to +230
auto *server = new QDBusServer(QString("unix:path=%1").arg(socketPath.c_str()),
QCoreApplication::instance());
if (!server->isConnected()) {
LogE("listen on socket: {}", server->lastError().message().toStdString());
LogE("listen on socket {}: {}",
socketPath.c_str(),
server->lastError().message().toStdString());
return -1;
}
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() {
if (QDir::root().remove("/tmp/linglong-package-manager.socket")) {
return;
}
LogE("failed to remove /tmp/linglong-package-manager.socket.");
});

// Set socket file permissions: only owner can read/write (0600)
if (chmod(socketPath.c_str(), S_IRUSR | S_IWUSR) != 0) {
LogE("Failed to set socket permissions: {}", strerror(errno));
return -1;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

There is a security race condition between the creation of the UNIX domain socket by QDBusServer and the subsequent chmod call. During this window, the socket file is created with default permissions (governed by the system umask, typically 0755 or 0777), which may allow other local users to connect to the socket before its permissions are restricted to 0600.

To prevent this race condition securely, set the process umask to 0077 before creating the QDBusServer and restore it afterward. This ensures the socket file is created with secure permissions (0600/0700) from the very beginning, and eliminates the need for a separate chmod call.

        // Set umask to 0077 to ensure the socket is created with secure permissions (0600/0700)
        // and avoid a race condition between socket creation and chmod.
        mode_t oldUmask = ::umask(0077);
        auto *server = new QDBusServer(QString("unix:path=%1").arg(socketPath.c_str()),
                                       QCoreApplication::instance());
        ::umask(oldUmask);

        if (!server->isConnected()) {
            LogE("listen on socket {}: {}",
                 socketPath.c_str(),
                 server->lastError().message().toStdString());
            return -1;
        }

@dengbo11 dengbo11 closed this Jun 12, 2026
@dengbo11 dengbo11 deleted the fix-security branch June 12, 2026 01:40
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.

2 participants