Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/probe/exec_probe.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// #857 probe: each step of openkal-linux 0.18.2's start of a program
// (src/process.cpp:684-712), by raw system call, with its errno. Prints and
// never asserts.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>

#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif

static void say(const char* step, long r) {
if (r < 0) printf("%-44s -> -1 errno=%d (%s)\n", step, errno, strerror(errno));
else printf("%-44s -> %ld\n", step, r);
fflush(stdout);
}

typedef long (*starter)(void*);
struct ctx { int base, exe; const char* abs; const char* name; char** argv; char** envp; };

static long do_execveat_empty(void* p) { struct ctx* c = p; return syscall(SYS_execveat, c->exe, "", c->argv, c->envp, AT_EMPTY_PATH); }
static long do_execveat_rel(void* p) { struct ctx* c = p; return syscall(SYS_execveat, c->base, c->name, c->argv, c->envp, 0); }
static long do_execve_abs(void* p) { struct ctx* c = p; return syscall(SYS_execve, c->abs, c->argv, c->envp); }
static long do_execveat_empty_noclo(void* p) {
struct ctx* c = p; fcntl(c->exe, F_SETFD, 0);
return syscall(SYS_execveat, c->exe, "", c->argv, c->envp, AT_EMPTY_PATH);
}

// Runs `f` in a child; the child writes its errno into a pipe when the exec fails.
static void in_child(const char* step, starter f, struct ctx* c) {
int pfd[2]; if (pipe2(pfd, O_CLOEXEC) != 0) { say("pipe2", -1); return; }
pid_t pid = fork();
if (pid == 0) {
close(pfd[0]);
int devnull = open("/dev/null", O_WRONLY); dup2(devnull, 1); dup2(devnull, 2);
f(c);
int e = errno; (void)!write(pfd[1], &e, sizeof e); _exit(127);
}
close(pfd[1]);
int e = 0; ssize_t n = read(pfd[0], &e, sizeof e); close(pfd[0]);
int st = 0; waitpid(pid, &st, 0);
if (n == sizeof e) printf("%-44s -> exec failed errno=%d (%s)\n", step, e, strerror(e));
else printf("%-44s -> exec ok, exit status %d\n", step, WIFEXITED(st) ? WEXITSTATUS(st) : -1);
fflush(stdout);
}

int main(int argc, char** argv, char** envp) {
if (argc < 3) { fprintf(stderr, "usage: %s <dir> <name> [args...]\n", argv[0]); return 2; }
struct ctx c = { .name = argv[2], .argv = argv + 2, .envp = envp };
char abs[4096]; snprintf(abs, sizeof abs, "%s/%s", argv[1], argv[2]); c.abs = abs;
printf("== target %s\n", abs);

c.base = open(argv[1], O_PATH | O_DIRECTORY | O_CLOEXEC); say("open(dir, O_PATH|O_DIRECTORY)", c.base);
c.exe = openat(c.base, argv[2], O_PATH | O_CLOEXEC); say("openat(base, name, O_PATH|O_CLOEXEC)", c.exe);

char link[64], named[4096]; snprintf(link, sizeof link, "/proc/self/fd/%d", c.base);
ssize_t n = readlink(link, named, sizeof named - 1); say("readlink(/proc/self/fd/<base>)", n);
if (n >= 0) { named[n] = 0; printf(" named = %s\n", named); }

struct stat a, b;
say("fstatat(exe, \"\", AT_EMPTY_PATH)", fstatat(c.exe, "", &a, AT_EMPTY_PATH));
say("stat(absolute name)", stat(abs, &b));
printf(" same_file = %d\n", a.st_dev == b.st_dev && a.st_ino == b.st_ino);
say("access(absolute, X_OK)", access(abs, X_OK));

in_child("execveat(exe, \"\", AT_EMPTY_PATH)", do_execveat_empty, &c);
in_child("execveat(exe, \"\", AT_EMPTY_PATH) without CLOEXEC", do_execveat_empty_noclo, &c);
in_child("execveat(base, name, 0)", do_execveat_rel, &c);
in_child("execve(absolute name)", do_execve_abs, &c);
return 0;
}
16 changes: 16 additions & 0 deletions .github/probe/inside_857.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/system/bin/sh
# Runs inside the app sandbox (run-as com.termux): the failing case of #857,
# mcpp new + mcpp build, the build traced by a static strace.
A=/data/data/com.termux/files
D="$1"
export HOME="$A/home" TMPDIR="$A/home/tmp"
mkdir -p "$TMPDIR"
cd "$HOME" && rm -rf hello
"$D/bin/mcpp" new hello 2>&1 | tail -5
cd hello
"$A/strace" -f -e trace=execve,execveat,openat,fchdir,clone,clone3,readlinkat,newfstatat,statx \
-o "$HOME/trace.log" "$D/bin/mcpp" build 2>&1 | tail -15
echo "--- trace: failing calls ---"
grep -E "= -1 (ENOENT|EACCES|EPERM|EBADF)" "$HOME/trace.log" | grep -v "/proc/sys\|locale\|zoneinfo\|\.mcpp-" | tail -60
echo "--- trace: every exec ---"
grep -E "execve" "$HOME/trace.log" | tail -30
51 changes: 51 additions & 0 deletions .github/probe/run_857.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# #857 probe: inside the Android 14 emulator, under `run-as com.termux`, every
# step of openkal-linux's start of a program, and mcpp itself. Prints, never asserts.
set -uo pipefail
WORK="${RUNNER_TEMP:-/tmp}/p857"
S="${GITHUB_STEP_SUMMARY:-/dev/null}"
A=/data/data/com.termux/files
adb wait-for-device
adb install -r "$WORK/termux.apk" >/dev/null
adb shell am start -n com.termux/com.termux.app.TermuxActivity >/dev/null
for _ in $(seq 1 60); do adb shell run-as com.termux ls "$A/usr/bin/sh" >/dev/null 2>&1 && break; sleep 5; done
for f in mcpp.tar.gz exec_probe strace inside_857.sh; do
adb push "$WORK/$f" /data/local/tmp/ >/dev/null
adb shell chmod 644 "/data/local/tmp/$f"
adb shell run-as com.termux cp "/data/local/tmp/$f" "$A/$f"
done
adb shell run-as com.termux chmod 755 "$A/exec_probe" "$A/strace"
adb logcat -c
run() { adb shell run-as com.termux "$@" 2>&1 | tr -d '\r'; }
{
echo "SELinux: $(adb shell getenforce | tr -d '\r'); context: $(run cat /proc/self/attr/current | tr -d '\0')"
echo "kernel: $(adb shell uname -a | tr -d '\r')"
run mkdir -p "$A/home/dist"
run /system/bin/tar -xzf "$A/mcpp.tar.gz" -C "$A/home/dist"
run /system/bin/ls -la "$A/home/dist"
D="$A/home/dist/$(run /system/bin/ls "$A/home/dist" | head -1)"
echo "release: $D"
echo; echo "### the bundled xlings (static ELF)"
run "$A/exec_probe" "$D/registry/bin" xlings --version
echo; echo "### mcpp itself"
run "$A/exec_probe" "$D/bin" mcpp --version
echo; echo "### toybox (system, bionic)"
run "$A/exec_probe" /system/bin toybox true
echo; echo "### a static ELF the app wrote, copied out of the release"
run cp "$D/registry/bin/xlings" "$A/home/xlings_copy"
run "$A/exec_probe" "$A/home" xlings_copy --version
echo; echo "### file attributes"
run /system/bin/ls -lZ "$D/registry/bin/xlings" "$D/bin/mcpp" "$A/home/xlings_copy" "$A/home/exec_probe_copy"
echo; echo "### the probe copying itself"
run cp "$A/exec_probe" "$A/home/exec_probe_copy"
run "$A/exec_probe" "$A/home" exec_probe_copy
echo; echo "### mcpp 2026.10.29.1 under run-as (the failing case)"
run /system/bin/env HOME="$A/home" TMPDIR="$A/home/tmp" "$D/bin/mcpp" --version 2>&1 | head -5
run /system/bin/env HOME="$A/home" TMPDIR="$A/home/tmp" "$D/bin/mcpp" self env 2>&1 | head -20
echo; echo "### the failing case reproduced: mcpp new + mcpp build, traced"
run /system/bin/sh "$A/inside_857.sh" "$D"

echo; echo "### avc denials"
adb logcat -d -b all 2>/dev/null | tr -d '\r' | grep -i 'avc' | tail -40
} | tee "$WORK/probe.log"
{ echo '### #857 probe'; echo '```'; cat "$WORK/probe.log"; echo '```'; } >> "$S"
163 changes: 0 additions & 163 deletions .github/workflows/aur-publish.yml

This file was deleted.

Loading
Loading