Skip to content

A started program whose lifetime is bound to the caller's, and the claim that execve is indistinguishable #23

Description

@Sunrisepeak

Two things, and the second is why the first matters.

1. The claim that execve-as-spawn+wait is indistinguishable is false

openkal-musl expresses execve as starting a program, waiting for it, and
ending with its status — clause 3.1 declines to simulate replacing an image, and
that is right. Both README.md and musl/PATCHES.md in that repository added
that "a caller cannot distinguish that through this library".

⚠️ A caller can, and the claim is what kept anyone from looking. Three
differences are now known; two were found by a consumer, not by us:

  1. When the program cannot be started. The replacement fails inside the
    duplicate, which ends with 127, so execve ended the caller with 127
    instead of returning -1. musl's execvp needs each execve to return so it
    can try the next PATH entry — so a program named without a separator was
    found only if it happened to sit in the first entry. (Answered in
    openkal-musl 0.10.0 by asking kal_fs_info first; the residue is below.)
  2. kill does not reach the program. This is what this issue asks about.
  3. The identifier the started program reports is not the caller's.

2. kill reaches the waiter, not the program

After fork + execve there are three images, not two: the copy is left
waiting for the program it started. A signal sent to the identifier the parent
holds reaches the waiter.

Measured, with the host as control — identical status words, opposite
outcomes
:

                    status    what actually happened
openkal-musl        0x000f    the program RAN TO COMPLETION, unsupervised
host glibc          0x000f    the program was killed

The parent is told the program died on the signal it sent. It did not.

kal_process_terminate is not at fault. It was asked to terminate one
started program and it did exactly that. What is missing is a way to say the
thing execve means: this program exists only to stand in for me, so its
lifetime is bound to mine.

The shape asked for

A spawn that states the binding — a flag on kal_spawn_streams' sibling
argument, or a kal_process_spawn_bound, whichever fits clause 8. Backends:

Linux prctl(PR_SET_PDEATHSIG, SIGKILL) in the started child
Windows a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
macOS no PDEATHSIG; a kqueue NOTE_EXIT watch, or the property is not claimed

Declaring it optional (a kal_process_props position) is consistent with how
KAL_PROCESS_PROP_STREAM_PASSING is already handled: the port asks only where a
caller actually needs it.

An alternative that was evaluated and rejected

Making kal_process_terminate kill a process group, with kal_space_start
putting each copy in one. It works for this case and breaks another: a new
process group leaves the terminal's foreground group, so in a program with a
terminal interface any child that reads the terminal takes SIGTTIN and stops.
That trades a visible defect for a much harder one.

Meanwhile

openkal-musl 0.10.0 records the divergence in both files, replaces the false
claim, and tells consumers to use posix_spawn, system or popen where they
need to stop what they started — kill reaches the program on all three of
those, which is measured.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions