mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Bound private token input without terminal truncation
The executable overflow regression and a bounded Bash PTY probe show canonical terminals truncate long input before the size check. Read a bounded character count with echo held off, drain an invalid line before returning to the calling shell, and restore its modes on every exit. Pin maximum-size preservation, overflow history safety and terminal restoration. Contract-Neutral: Repair bounded private terminal entry, overflow draining and mode restoration; no issuance, scope, install transport or response-shape delta. Change-source: pulse-maintainer
This commit is contained in:
@@ -47,6 +47,52 @@ func TestContainerBootstrapHistoryAndShellData(t *testing.T) {
|
||||
assertContainerBootstrapCleanup(t, root)
|
||||
}
|
||||
|
||||
func TestContainerBootstrapOverflowDoesNotBecomeShellHistory(t *testing.T) {
|
||||
root, env := bootstrapFixture(t, recordingInstaller)
|
||||
token := strings.Repeat("x", 10000) + "-OVERFLOW_HISTORY_MARKER"
|
||||
command := BuildContainerRuntimeAgentInstallCommand("https://pulse.example", "separate-issued-token", true)
|
||||
history := filepath.Join(root, "shell-history")
|
||||
payload, err := json.Marshal(map[string]string{"command": command, "input": token, "history": history})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("python3", "-c", bootstrapPTYRunner)
|
||||
cmd.Stdin = strings.NewReader(string(payload))
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatal("overflowed input was accepted")
|
||||
}
|
||||
hist, err := os.ReadFile(history)
|
||||
if err != nil || !strings.Contains(string(hist), command) {
|
||||
t.Fatal("history control did not record the copied command")
|
||||
}
|
||||
for _, output := range []string{string(out), string(hist)} {
|
||||
if strings.Contains(output, "OVERFLOW_HISTORY_MARKER") || strings.Contains(output, strings.Repeat("x", 512)) {
|
||||
t.Fatal("discarded credential input was echoed or returned to shell history")
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(root, "captured-token")); !os.IsNotExist(err) {
|
||||
t.Fatal("installer ran after overflowed input")
|
||||
}
|
||||
assertContainerBootstrapCleanup(t, root)
|
||||
}
|
||||
|
||||
func TestContainerBootstrapPreservesMaximumAcceptedInput(t *testing.T) {
|
||||
root, env := bootstrapFixture(t, recordingInstaller)
|
||||
token := strings.Repeat("a", 4096)
|
||||
command := BuildContainerRuntimeAgentInstallCommand("https://pulse.example", "separate-issued-token", true)
|
||||
out, err := runBootstrap(t, command, token, env, true)
|
||||
if err != nil {
|
||||
t.Fatalf("maximum-size private input: %v\n%s", err, out)
|
||||
}
|
||||
captured, err := os.ReadFile(filepath.Join(root, "captured-token"))
|
||||
if err != nil || string(captured) != token {
|
||||
t.Fatal("terminal silently truncated an accepted token")
|
||||
}
|
||||
assertContainerBootstrapCleanup(t, root)
|
||||
}
|
||||
|
||||
func assertContainerBootstrapCleanup(t *testing.T, root string) {
|
||||
t.Helper()
|
||||
directories, err := os.ReadFile(filepath.Join(root, "private-directories"))
|
||||
|
||||
@@ -83,9 +83,18 @@ func privateBootstrapCommand(scriptURL, curlFlags, preflight, run string, needsT
|
||||
privileged = append(privileged,
|
||||
`token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);`,
|
||||
`token_file="$token_dir/token";`,
|
||||
`cleanup() { unset pulse_token; rm -f -- "$token_file"; rmdir -- "$token_dir"; };`,
|
||||
`unset pulse_token pulse_discard tty_state;`,
|
||||
`cleanup() { unset pulse_token pulse_discard; if [ -n "${tty_state:-}" ]; then stty "$tty_state" </dev/tty 2>/dev/null || :; fi; rm -f -- "$token_file"; rmdir -- "$token_dir"; };`,
|
||||
`trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;`,
|
||||
`if ! IFS= read -r -s -p `+posixShellQuote(prompt+" (paste at this prompt, not in the command): ")+` pulse_token </dev/tty; then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;`,
|
||||
// A normal canonical tty silently truncates long lines (4095 bytes on
|
||||
// Linux). Bounded character input preserves valid input and detects
|
||||
// overflow; keep echo off while draining the rest of an invalid line
|
||||
// so it cannot become a second command in the caller's shell history.
|
||||
`if ! tty_state=$(stty -g </dev/tty); then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;`,
|
||||
`stty -echo </dev/tty;`,
|
||||
`if ! IFS= read -r -s -p `+posixShellQuote(prompt+" (paste at this prompt, not in the command): ")+` -n 4097 pulse_token </dev/tty; then echo "Token input was interrupted. No installation was attempted." >&2; exit 1; fi;`,
|
||||
`if [ "${#pulse_token}" -gt 4096 ]; then while IFS= read -r -s -n 4097 pulse_discard </dev/tty && [ "${#pulse_discard}" -eq 4097 ]; do :; done; unset pulse_discard; fi;`,
|
||||
`stty "$tty_state" </dev/tty; unset tty_state;`,
|
||||
`printf '\n' >/dev/tty;`,
|
||||
`if [ -z "$pulse_token" ] || [ "${#pulse_token}" -gt 4096 ]; then echo "A non-empty token of at most 4096 characters is required." >&2; exit 1; fi;`,
|
||||
`printf %s "$pulse_token" > "$token_file"; unset pulse_token;`,
|
||||
|
||||
@@ -16,6 +16,8 @@ const bootstrapPTYRunner = `
|
||||
import errno, fcntl, json, os, pty, select, signal, subprocess, sys, termios, time
|
||||
p = json.load(sys.stdin)
|
||||
master, slave = pty.openpty()
|
||||
tty_watch = os.dup(slave)
|
||||
initial_tty = termios.tcgetattr(tty_watch)
|
||||
def session():
|
||||
os.setsid()
|
||||
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
|
||||
@@ -48,6 +50,12 @@ else:
|
||||
os.killpg(child.pid, signal.SIGKILL)
|
||||
output += b"\nPTY fixture deadline expired\n"
|
||||
code = child.wait()
|
||||
final_tty = termios.tcgetattr(tty_watch)
|
||||
mode_mask = termios.ECHO | termios.ICANON
|
||||
if (initial_tty[3] & mode_mask) != (final_tty[3] & mode_mask):
|
||||
output += b"\nBootstrap did not restore terminal echo/canonical mode\n"
|
||||
code = 1
|
||||
os.close(tty_watch)
|
||||
os.close(master)
|
||||
sys.stdout.buffer.write(output)
|
||||
sys.exit(code if code >= 0 else 128 - code)
|
||||
|
||||
Reference in New Issue
Block a user