Skip to content

Conversation

@wineee
Copy link
Member

@wineee wineee commented Dec 24, 2025

…-Bus

Log: ExecSearchPath must be absolute and must not contain a ".." path component

Summary by Sourcery

Normalize and validate the ExecSearchPath property before sending it over D-Bus to ensure only absolute, well-formed paths are used.

Bug Fixes:

  • Reject ExecSearchPath entries that are empty, non-absolute, contain control characters, or normalize to non-absolute paths.

Enhancements:

  • Lexically normalize ExecSearchPath entries, trimming trailing slashes and deduplicating paths before inclusion in D-Bus messages.

@wineee wineee requested a review from Copilot December 24, 2025 06:02
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 24, 2025

Reviewer's Guide

Normalizes and validates ExecSearchPath values before sending them over D-Bus by introducing a dedicated normalization function and wiring it into the property-serialization path.

Sequence diagram for processing ExecSearchPath property

sequenceDiagram
    participant processKVPair
    participant normalizeExecSearchPath
    participant sd_bus_message as sd_bus_message
    participant sd_journal as sd_journal

    processKVPair->>processKVPair: Iterate over props map
    alt key is ExecSearchPath
        processKVPair->>normalizeExecSearchPath: normalizeExecSearchPath(value)
        normalizeExecSearchPath-->>processKVPair: normalizedStorage (vector<string>)
        processKVPair->>processKVPair: Build normalizedValue list from normalizedStorage
        alt normalizedValue is empty
            processKVPair->>sd_journal: LOG_WARNING ExecSearchPath normalized to empty
            processKVPair-->>processKVPair: Continue to next property (skip serialization)
        else normalizedValue not empty
            processKVPair-->>processKVPair: valuePtr = &normalizedValue
        end
    else key is not ExecSearchPath
        processKVPair-->>processKVPair: valuePtr = &value (original)
    end

    processKVPair->>sd_bus_message: sd_bus_message_open_container(struct sv)
    processKVPair->>sd_bus_message: sd_bus_message_append_basic(key)
    processKVPair->>sd_bus_message: sd_bus_message_open_container(variant)
    processKVPair->>sd_bus_message: appendPropValue(getPropType(key), *valuePtr)
    processKVPair->>sd_bus_message: sd_bus_message_close_container(variant)
    processKVPair->>sd_bus_message: sd_bus_message_close_container(struct)
Loading

File-Level Changes

Change Details Files
Add ExecSearchPath normalization helper to sanitize, normalize, and deduplicate path entries.
  • Introduce normalizeExecSearchPath() that processes a list of string_view paths into a normalized vector of strings
  • Reject empty, non-absolute, and control-character-containing paths, logging a notice for skipped entries
  • Use std::filesystem::path::lexically_normal to fold '.' and '..' components, then trim trailing slashes except for root
  • Ensure resulting paths remain absolute and deduplicate them using an unordered_set before returning
apps/app-launch-helper/src/main.cpp
Integrate ExecSearchPath normalization into property processing before sending over D-Bus.
  • In processKVPair, detect the ExecSearchPath key and run its values through normalizeExecSearchPath
  • Rebuild a list<string_view> backed by the normalized string storage and pass that to appendPropValue instead of the original list
  • Skip emitting the ExecSearchPath property entirely and log a warning if normalization yields no valid entries
apps/app-launch-helper/src/main.cpp

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 3 issues, and left some high level feedback:

  • The new ExecSearchPath normalization path goes through a temporary std::vector<std::string> and then std::list<std::string_view>; double-check that appendPropValue and the D-Bus marshalling do not retain these string_views beyond the scope of processKVPair, as they now point to short‑lived strings rather than the original backing storage.
  • In normalizeExecSearchPath, consider reserving capacity for result and dedup based on value.size() to avoid repeated allocations when normalizing larger ExecSearchPath lists.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new ExecSearchPath normalization path goes through a temporary `std::vector<std::string>` and then `std::list<std::string_view>`; double-check that `appendPropValue` and the D-Bus marshalling do not retain these `string_view`s beyond the scope of `processKVPair`, as they now point to short‑lived strings rather than the original backing storage.
- In `normalizeExecSearchPath`, consider reserving capacity for `result` and `dedup` based on `value.size()` to avoid repeated allocations when normalizing larger ExecSearchPath lists.

## Individual Comments

### Comment 1
<location> `apps/app-launch-helper/src/main.cpp:168-175` </location>
<code_context>
+
+        // Reject control characters (e.g. \n, \t, \0). Align closer to systemd.
+        bool hasCtrl = false;
+        for (char ch : v) {
+            if (std::iscntrl(static_cast<unsigned char>(ch))) {
+                hasCtrl = true;
+                break;
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider whether rejecting all control characters is too strict or locale-dependent for valid paths.

`std::iscntrl` is locale-dependent and will reject any locale-specific control characters across the entire path. On systems using non-UTF-8 or other encodings, this may reject paths that the filesystem accepts. If you only want ASCII control characters, consider checking `ch < 0x20 || ch == 0x7f` instead to avoid locale dependence and clarify the intended policy.

```suggestion
        // Reject ASCII control characters (e.g. \n, \t, \0) to align closer to systemd and avoid locale-dependent behavior.
        bool hasCtrl = false;
        for (unsigned char ch : v) {
            if (ch < 0x20 || ch == 0x7f) {
                hasCtrl = true;
                break;
            }
        }
```
</issue_to_address>

### Comment 2
<location> `apps/app-launch-helper/src/main.cpp:248-255` </location>
<code_context>
+            std::list<std::string_view> normalizedValue;
+            std::vector<std::string> normalizedStorage;
+
+            if (key == "ExecSearchPath") {
+                normalizedStorage = normalizeExecSearchPath(value);
+                for (const auto &s : normalizedStorage) {
+                    normalizedValue.emplace_back(s);
+                }
+
+                if (normalizedValue.empty()) {
+                    sd_journal_print(LOG_WARNING, "ExecSearchPath normalized to empty, skipping property");
+                    continue;
+                }
</code_context>

<issue_to_address>
**suggestion (performance):** Multiple journal logs per malformed `ExecSearchPath` entry may be noisy in real deployments.

With `LOG_NOTICE` in `normalizeExecSearchPath` plus this `LOG_WARNING`, a single bad or frequently-used `ExecSearchPath` could flood the journal. If this code runs often (e.g., every launch), consider reducing the per-entry log level, aggregating messages, or adding rate limiting to avoid excessive log noise.

Suggested implementation:

```cpp
            if (key == "ExecSearchPath") {
                normalizedStorage = normalizeExecSearchPath(value);
                for (const auto &s : normalizedStorage) {
                    normalizedValue.emplace_back(s);
                }

                if (normalizedValue.empty()) {
                    /* Log at most once per process to avoid flooding the journal on frequently-used bad ExecSearchPath values. */
                    static bool exec_search_path_empty_logged = false;
                    if (!exec_search_path_empty_logged) {
                        sd_journal_print(LOG_NOTICE, "ExecSearchPath normalized to empty, skipping property (further occurrences will be suppressed)");
                        exec_search_path_empty_logged = true;
                    }
                    continue;
                }

```

You also mentioned `LOG_NOTICE` usage inside `normalizeExecSearchPath`. To fully address log noise:

1. Locate any `sd_journal_print(LOG_NOTICE, ...)` calls inside `normalizeExecSearchPath` and either:
   - Downgrade them to `LOG_DEBUG` or `LOG_INFO`, or
   - Guard them with a similar static “logged once” flag, or
   - Aggregate them (e.g., count invalid entries and log a single summary message per call).
2. If `normalizeExecSearchPath` may be called in tight loops or for many entries, prefer either aggregation per call or DEBUG-level logs to minimize noise in production.

Because the body of `normalizeExecSearchPath` is not fully visible in the snippet, those adjustments must be made where its logging statements are defined.
</issue_to_address>

### Comment 3
<location> `apps/app-launch-helper/src/main.cpp:209` </location>
<code_context>
+    return result;
+}
+
 int appendPropValue(msg_ptr &msg, DBusValueType type, const std::list<std::string_view> &value)
 {
     int ret;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the ExecSearchPath handling by moving container adaptation into an appendPropValue overload and splitting normalization into small helpers to simplify control flow and improve readability.

You can keep the behavior but significantly reduce the “container dance” and control-flow indirection around `ExecSearchPath` by:

1. **Localizing the container juggling inside `appendPropValue` via an overload**, instead of in `processKVPair`.
2. **Splitting `normalizeExecSearchPath` into small helpers** to make it easier to read/maintain.

### 1. Simplify `processKVPair` by adding an overload of `appendPropValue`

Instead of `valuePtr`, `normalizedValue`, and `normalizedStorage` plus a pointer switch, push the adaptation into `appendPropValue`:

```cpp
int appendPropValue(msg_ptr &msg, DBusValueType type,
                    const std::list<std::string_view> &value);

int appendPropValue(msg_ptr &msg, DBusValueType type,
                    const std::vector<std::string> &value)
{
    // Reuse existing implementation by adapting locally here
    std::list<std::string_view> views;
    views.reserve(value.size()); // if using a small helper container, keep it cheap
    for (const auto &s : value) {
        views.emplace_back(s);
    }
    return appendPropValue(msg, type, views);
}
```

Then `processKVPair` can be simplified to straight-line logic with two explicit branches and no pointer indirection:

```cpp
int processKVPair(msg_ptr &msg,
                  const std::map<std::string_view, std::list<std::string_view>> &props)
{
    int ret;
    if (!props.empty()) {
        for (auto [key, value] : props) {
            std::string keyStr{key};

            if (ret = sd_bus_message_open_container(msg, SD_BUS_TYPE_STRUCT, "sv"); ret < 0) {
                sd_journal_perror("open struct of properties failed.");
                return ret;
            }

            if (ret = sd_bus_message_append(msg, "s", keyStr.data()); ret < 0) {
                sd_journal_perror("append key of property failed.");
                return ret;
            }

            if (key == "ExecSearchPath") {
                std::vector<std::string> normalized = normalizeExecSearchPath(value);
                if (normalized.empty()) {
                    sd_journal_print(LOG_WARNING,
                                     "ExecSearchPath normalized to empty, skipping property");
                    // Skip closing value, just close the struct
                    if (ret = sd_bus_message_close_container(msg); ret < 0) {
                        sd_journal_perror("close struct of properties failed.");
                        return ret;
                    }
                    continue;
                }

                if (ret = appendPropValue(msg, getPropType(key), normalized); ret < 0) {
                    sd_journal_perror("append value of property failed.");
                    return ret;
                }
            } else {
                if (ret = appendPropValue(msg, getPropType(key), value); ret < 0) {
                    sd_journal_perror("append value of property failed.");
                    return ret;
                }
            }

            if (ret = sd_bus_message_close_container(msg); ret < 0) {
                sd_journal_perror("close struct of properties failed.");
                return ret;
            }
        }
    }
    return 0;
}
```

This keeps all existing behavior but:

- Removes `valuePtr`, `normalizedValue`, and the implicit lifetime coupling between `normalizedStorage` and `normalizedValue`.
- Makes the ExecSearchPath special case explicit and easy to follow.
- Contains the `vector<string> -> list<string_view>` conversion in one place (`appendPropValue` overload), which is where that adaptation logically belongs.

### 2. Decompose `normalizeExecSearchPath` into small helpers

The normalization logic is correct but dense. Extracting small helpers reduces cognitive load without changing behavior:

```cpp
bool containsControlChars(std::string_view v)
{
    for (char ch : v) {
        if (std::iscntrl(static_cast<unsigned char>(ch))) {
            return true;
        }
    }
    return false;
}

std::string normalizePathString(std::string_view v)
{
    std::filesystem::path p{std::string{v}};
    auto norm = p.lexically_normal().string();
    while (norm.size() > 1 && norm.back() == '/') {
        norm.pop_back();
    }
    return norm;
}

bool isValidAbsolutePath(std::string_view v)
{
    return !v.empty() && v.front() == '/';
}
```

Then `normalizeExecSearchPath` becomes much clearer:

```cpp
std::vector<std::string> normalizeExecSearchPath(
    const std::list<std::string_view> &value)
{
    std::vector<std::string> result;
    std::unordered_set<std::string> dedup;

    for (const auto &v : value) {
        if (v.empty()) {
            continue;
        }

        if (containsControlChars(v)) {
            sd_journal_print(LOG_NOTICE, "ExecSearchPath skip control-chars present");
            continue;
        }

        if (!isValidAbsolutePath(v)) {
            sd_journal_print(LOG_NOTICE, "ExecSearchPath skip non-absolute: %.*s",
                             static_cast<int>(v.size()), v.data());
            continue;
        }

        std::string norm = normalizePathString(v);

        if (!isValidAbsolutePath(norm)) {
            sd_journal_print(LOG_NOTICE,
                             "ExecSearchPath skip normalized non-absolute: %s",
                             norm.c_str());
            continue;
        }

        if (!dedup.emplace(norm).second) {
            continue;
        }

        result.emplace_back(std::move(norm));
    }

    return result;
}
```

All original checks and behavior remain, but each concern is in a named helper, which makes the overall feature easier to understand and maintain.
</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.

@wineee
Copy link
Member Author

wineee commented Dec 24, 2025

pipx 为我PATH设置了 /home/deepin/.local/share/../bin

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds normalization and validation of the ExecSearchPath property before sending it to D-Bus in the app-launch-helper. The implementation ensures that only absolute, well-formed paths without control characters or ".." components are accepted, and duplicates are removed.

Key changes:

  • Added normalizeExecSearchPath() function to validate, normalize, and deduplicate path entries
  • Integrated path normalization into the D-Bus message preparation flow in processKVPair()
  • Added validation to reject empty paths, non-absolute paths, paths with control characters, and paths that normalize to non-absolute

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 169 to 175
bool hasCtrl = false;
for (char ch : v) {
if (std::iscntrl(static_cast<unsigned char>(ch))) {
hasCtrl = true;
break;
}
}
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Consider extracting the control character detection into a separate helper function or using std::any_of for better readability and to follow functional programming patterns used elsewhere in the codebase.

Copilot uses AI. Check for mistakes.
}
}
if (hasCtrl) {
sd_journal_print(LOG_NOTICE, "ExecSearchPath skip control-chars present");
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The error message should include the actual path that was skipped to aid in debugging. Consider logging which path entry contained control characters, similar to how non-absolute paths are logged on line 182.

Suggested change
sd_journal_print(LOG_NOTICE, "ExecSearchPath skip control-chars present");
sd_journal_print(LOG_NOTICE, "ExecSearchPath skip control-chars present: %.*s",
static_cast<int>(v.size()), v.data());

Copilot uses AI. Check for mistakes.
@wineee wineee force-pushed the fix branch 3 times, most recently from 4fec418 to e8cccdd Compare December 24, 2025 08:55
…-Bus

Log: ExecSearchPath must be absolute and must not contain a ".." path
component
@deepin-ci-robot
Copy link

deepin pr auto review

我来对这个diff进行详细的代码审查:

  1. 语法逻辑:
  • 代码语法正确,使用了C++17的filesystem特性
  • 逻辑流程清晰,对ExecSearchPath特殊处理,其他属性保持原有逻辑
  • 使用了指针切换来改变实际使用的值,这是一个巧妙的设计
  1. 代码质量:
    优点:
  • 添加了适当的错误处理和日志记录
  • 使用了RAII风格的变量声明
  • 代码结构清晰,职责分明

可改进之处:

  • 建议将ExecSearchPath的处理逻辑抽取为独立函数,提高代码可维护性
  • 可以考虑使用const auto&来遍历value,减少不必要的拷贝
  • normalizedValue和normalizedStorage的声明可以移到if作用域内,提高局部性
  1. 代码性能:
  • 使用std::filesystem::path进行路径处理会有一定性能开销,但考虑到这是启动辅助程序,影响不大
  • normalizedStorage使用vector存储string,然后转换为string_view,这个转换过程可以优化
  • 建议预分配normalizedStorage的大小,避免多次重新分配
  1. 代码安全:
  • 使用std::string{v}的转换是安全的
  • 文件系统路径的规范化处理增加了安全性
  • 日志记录使用sd_journal_print是线程安全的

改进建议:

// 建议抽取为独立函数
std::list<std::string_view> normalizeExecSearchPaths(const std::list<std::string_view>& value) {
    std::vector<std::string> normalizedStorage;
    normalizedStorage.reserve(value.size());  // 预分配空间
    
    for (const auto& v : value) {
        std::filesystem::path p{std::string{v}};
        if (!p.is_absolute()) {
            sd_journal_print(LOG_INFO, "ExecSearchPath ignoring relative path: %s", std::string{v}.c_str());
            continue;
        }
        normalizedStorage.emplace_back(p.lexically_normal().string());
    }

    if (normalizedStorage.empty()) {
        sd_journal_print(LOG_WARNING, "ExecSearchPath normalized to empty, skipping property");
        return {};
    }

    return std::list<std::string_view>(normalizedStorage.begin(), normalizedStorage.end());
}

// 在主函数中使用
if (key == "ExecSearchPath") {
    normalizedValue = normalizeExecSearchPaths(value);
    if (normalizedValue.empty()) {
        continue;
    }
    valuePtr = &normalizedValue;
}

这个改进版本:

  1. 提高了代码的可维护性和可读性
  2. 通过预分配空间优化了性能
  3. 保持了原有的安全性
  4. 逻辑更清晰,职责更单一

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: wineee, zccrs

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

@zccrs zccrs merged commit 2562419 into linuxdeepin:master Dec 24, 2025
15 of 17 checks passed
@wineee wineee deleted the fix branch December 24, 2025 09:50
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