Skip to content

Conversation

@jpnurmi
Copy link
Collaborator

@jpnurmi jpnurmi commented Jul 18, 2025

For spawning a fully detached subprocess (feedback handler / crash reporter) that continues running independently from the (potentially crashed) parent process. Extracted from #1303.

Linux

Classic double-fork() plus setsid() in between with stdin/out/err detached, and execv() or execvp() (same as what Crashpad does too).

macOS

Same as Linux except that .app bundles are launched with open -a <bundle> --args <args..> to make macOS activate the app correctly and bring it to the foreground.

Windows

CreateProcessW + DETACHED_PROCESS

#skip-changelog

@jpnurmi jpnurmi requested review from JoshuaMoelans and vaind July 18, 2025 08:54
@github-actions
Copy link

github-actions bot commented Jul 18, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against f370a7a

Copy link
Contributor

@vaind vaind left a comment

Choose a reason for hiding this comment

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

Potential NULL pointer dereference

Both Unix and Windows implementations have a potential NULL pointer dereference when checking the executable path.

Unix implementation (src/process/sentry_process_unix.c:151):

    if (\!executable || \!executable->path || strcmp(executable->path, "") == 0) {
        return false;
    }

Windows implementation (src/process/sentry_process_windows.c:12):

    if (\!executable || \!executable->path || wcscmp(executable->path, L"") == 0) {
        return false;
    }

Issue: The code checks \!executable but then directly accesses executable->path without verifying it's not NULL. Since sentry_path_t contains a sentry_pathchar_t *path field, this could be NULL even when the struct itself is valid, leading to a crash.

Copy link
Contributor

@vaind vaind left a comment

Choose a reason for hiding this comment

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

Logic Error: False Success in Double-Fork Pattern

The Unix implementation has a fundamental design flaw in the double-fork pattern where sentry__process_spawn() returns true even when the target executable fails to launch.

Logic Analysis:

  1. Parent process calls fork() → creates first child (pid1)
  2. First child calls setsid() and fork() again → creates second child (pid2, the grandchild)
  3. Second child (grandchild) calls execv() - this is where the target process actually runs
  4. First child immediately calls _exit(0) after creating the grandchild
  5. Parent process waits for the first child to exit with waitpid(pid1, &status, 0)

The Problem:

// In spawn_process():
if (pid2 == 0) {
    // second child (grandchild): actual execv() happens here
    execv(argv[0], argv);
    SENTRY_ERRORF("execv failed: %s", strerror(errno));
    _exit(1);  // ← This failure is never seen by parent
} else {
    // first child exits immediately after fork
    _exit(0);  // ← Parent only sees this exit code
}

Issue Flow:

  1. Parent waits for first child with waitpid(pid1, &status, 0)
  2. First child exits with code 0 after successful second fork
  3. If execv() fails in grandchild, it exits with code 1
  4. But parent already received exit code 0 from first child → returns true

Impact: Function returns true for non-existent executables, making the boolean return value misleading.

Note: This is inherent to fully detached processes - you can't reliably detect launch success without IPC. Consider either:

  • Documenting that true only means "detachment succeeded"
  • Changing return type to void
  • Adding optional launch verification mechanism

This affects the Unix implementation only (lines 84-146).


Comment by Claude Code

@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Jul 18, 2025

@vaind
Copy link
Contributor

vaind commented Jul 18, 2025

SENTRY_TEST(process_spawn) seems somehow unstable or problematic when running with Valgrind:

Claude says it's because of the previous comment about return values.

The valgrind failure I identified earlier is still valid because:

  1. The same logic flaw remains: The function still returns true when the first child exits successfully (line 144), even if the actual execv() in the
    grandchild fails.
  2. The test still fails: The test expects sentry__process_spawn() to return true when the process is successfully spawned, but due to the logic flaw, it may
    return true even when the cp command fails.
  3. Memory leak still occurs: Because the test fails at the assertion, it doesn't reach the cleanup code that would free the allocated paths.

Current Status

The CI is still failing with the same valgrind error and the same fundamental issue:

  • Test failure: TEST_CHECK(sentry__process_spawn(cp, exe->path, dst->path, NULL)) fails
  • Memory leak: Because the test fails, sentry__path_free(exe) is never reached
  • Root cause: The double-fork logic doesn't properly detect when the target process fails to launch

The code changes I can see (NULL pointer checks) were good fixes, but the fundamental double-fork logic issue remains. The function will return true as long as
the fork operations succeed, regardless of whether the target executable actually runs.

To fix this properly, the implementation would need to either:

  1. Accept the limitation: Document that the return value only indicates fork success, not process launch success
  2. Use a different approach: Implement some form of IPC to detect actual launch failures
  3. Change the API: Make the function return void since reliable success detection isn't possible with full process detachment

The current implementation creates a false expectation that launch failures can be detected, which they cannot be with the double-fork pattern.

jpnurmi added 2 commits July 18, 2025 15:10
just to see if the CI/Valgrind is happy with the rest
as claude put it

> This is inherent to fully detached processes - you can't reliably detect launch success without IPC
Copy link
Contributor

@vaind vaind left a comment

Choose a reason for hiding this comment

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

LGTM

jpnurmi added a commit that referenced this pull request Jul 19, 2025
jpnurmi added a commit that referenced this pull request Jul 19, 2025
jpnurmi added a commit that referenced this pull request Jul 28, 2025
jpnurmi added a commit that referenced this pull request Jul 29, 2025
@jpnurmi jpnurmi mentioned this pull request Jul 29, 2025
7 tasks
Copy link
Member

@JoshuaMoelans JoshuaMoelans left a comment

Choose a reason for hiding this comment

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

LGTM, although it might make sense to document how this API should only ever be called internally (and not with any arguments coming from user input), as otherwise passing the constructed cli value to the CreateProcessW on Windows could potentially be open to injection attacks.

Alternatively we could sanitize the arguments, but I'm not sure that is strictly necessary.

cursor[bot]

This comment was marked as outdated.

@jpnurmi jpnurmi merged commit 93d69f4 into master Jul 30, 2025
65 of 67 checks passed
@jpnurmi jpnurmi deleted the feat/process branch July 30, 2025 11:30
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.

4 participants