fix(ui): enforce async-signal-safe exit, blocking waitpid reaping, and unsetenv env isolation inside child PTY process
This commit is contained in:
BIN
Binary file not shown.
@@ -53,6 +53,12 @@ typedef _chdir_dart = int Function(ffi.Pointer<ffi.Char> path);
|
|||||||
typedef _fcntl_c = ffi.Int32 Function(ffi.Int32 fd, ffi.Int32 cmd, ffi.Int32 arg);
|
typedef _fcntl_c = ffi.Int32 Function(ffi.Int32 fd, ffi.Int32 cmd, ffi.Int32 arg);
|
||||||
typedef _fcntl_dart = int Function(int fd, int cmd, int arg);
|
typedef _fcntl_dart = int Function(int fd, int cmd, int arg);
|
||||||
|
|
||||||
|
typedef _unsetenv_c = ffi.Int32 Function(ffi.Pointer<ffi.Char> name);
|
||||||
|
typedef _unsetenv_dart = int Function(ffi.Pointer<ffi.Char> name);
|
||||||
|
|
||||||
|
typedef _exit_c = ffi.Void Function(ffi.Int32 status);
|
||||||
|
typedef _exit_dart = void Function(int status);
|
||||||
|
|
||||||
base class Winsize extends ffi.Struct {
|
base class Winsize extends ffi.Struct {
|
||||||
@ffi.Uint16()
|
@ffi.Uint16()
|
||||||
external int ws_row;
|
external int ws_row;
|
||||||
@@ -101,6 +107,8 @@ class PtySession {
|
|||||||
final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl');
|
final ioctl = libc.lookupFunction<_ioctl_c, _ioctl_dart>('ioctl');
|
||||||
final chdir = libc.lookupFunction<_chdir_c, _chdir_dart>('chdir');
|
final chdir = libc.lookupFunction<_chdir_c, _chdir_dart>('chdir');
|
||||||
final fcntl = libc.lookupFunction<_fcntl_c, _fcntl_dart>('fcntl');
|
final fcntl = libc.lookupFunction<_fcntl_c, _fcntl_dart>('fcntl');
|
||||||
|
final unsetenv = libc.lookupFunction<_unsetenv_c, _unsetenv_dart>('unsetenv');
|
||||||
|
final cExit = libc.lookupFunction<_exit_c, _exit_dart>('exit');
|
||||||
|
|
||||||
// 1. Prepare master PTY
|
// 1. Prepare master PTY
|
||||||
final masterFd = posixOpenpt(2 | 0x00000400);
|
final masterFd = posixOpenpt(2 | 0x00000400);
|
||||||
@@ -132,16 +140,15 @@ class PtySession {
|
|||||||
fcntl(masterFd, 4, flags | 2048);
|
fcntl(masterFd, 4, flags | 2048);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Resolve environment, isolating nested TMUX contexts (§6.7)
|
// 2. Pre-allocate ALL structures on the heap before fork() for async-signal-safety (§6.7)
|
||||||
final env = Map<String, String>.from(environment ?? Platform.environment);
|
|
||||||
env.remove('TMUX');
|
|
||||||
env.remove('TMUX_PANE');
|
|
||||||
|
|
||||||
// 3. Pre-allocate ALL structures on the heap before fork() for async-signal-safety (§6.7)
|
|
||||||
final pathPtr = slaveName.toNativeUtf8();
|
final pathPtr = slaveName.toNativeUtf8();
|
||||||
final exePtr = executable.toNativeUtf8();
|
final exePtr = executable.toNativeUtf8();
|
||||||
final workDirPtr = workingDirectory?.toNativeUtf8();
|
final workDirPtr = workingDirectory?.toNativeUtf8();
|
||||||
|
|
||||||
|
// Environment isolation variables to be cleared in child context
|
||||||
|
final tmuxNamePtr = 'TMUX'.toNativeUtf8();
|
||||||
|
final tmuxPaneNamePtr = 'TMUX_PANE'.toNativeUtf8();
|
||||||
|
|
||||||
final argsCount = arguments.length;
|
final argsCount = arguments.length;
|
||||||
final argv = malloc<ffi.Pointer<ffi.Char>>(argsCount + 2);
|
final argv = malloc<ffi.Pointer<ffi.Char>>(argsCount + 2);
|
||||||
argv[0] = exePtr.cast<ffi.Char>();
|
argv[0] = exePtr.cast<ffi.Char>();
|
||||||
@@ -154,12 +161,14 @@ class PtySession {
|
|||||||
}
|
}
|
||||||
argv[argsCount + 1] = ffi.Pointer.fromAddress(0);
|
argv[argsCount + 1] = ffi.Pointer.fromAddress(0);
|
||||||
|
|
||||||
// 4. Fork child process
|
// 3. Fork child process
|
||||||
final pid = fork();
|
final pid = fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
close(masterFd);
|
close(masterFd);
|
||||||
malloc.free(pathPtr);
|
malloc.free(pathPtr);
|
||||||
malloc.free(exePtr);
|
malloc.free(exePtr);
|
||||||
|
malloc.free(tmuxNamePtr);
|
||||||
|
malloc.free(tmuxPaneNamePtr);
|
||||||
if (workDirPtr != null) malloc.free(workDirPtr);
|
if (workDirPtr != null) malloc.free(workDirPtr);
|
||||||
malloc.free(argv);
|
malloc.free(argv);
|
||||||
for (final p in allocatedArgs) {
|
for (final p in allocatedArgs) {
|
||||||
@@ -169,12 +178,18 @@ class PtySession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
// --- CHILD PROCESS BRANCH (Strictly async-signal-safe syscalls only!) ---
|
// --- CHILD PROCESS BRANCH (Strictly async-signal-safe OS calls only!) ---
|
||||||
|
// A. Disassociate controlling terminal
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
|
// B. Isolate from nested TMUX environments (§6.7)
|
||||||
|
unsetenv(tmuxNamePtr.cast<ffi.Char>());
|
||||||
|
unsetenv(tmuxPaneNamePtr.cast<ffi.Char>());
|
||||||
|
|
||||||
|
// C. Open slave and redirect stdio
|
||||||
final slaveFd = open(pathPtr.cast<ffi.Char>(), 2);
|
final slaveFd = open(pathPtr.cast<ffi.Char>(), 2);
|
||||||
if (slaveFd < 0) {
|
if (slaveFd < 0) {
|
||||||
exit(-1);
|
cExit(-1); // direct libc _exit to prevent async-signal-unsafe Dart runtime exit
|
||||||
}
|
}
|
||||||
|
|
||||||
ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0));
|
ioctl(slaveFd, 0x540E, ffi.Pointer.fromAddress(0));
|
||||||
@@ -193,12 +208,14 @@ class PtySession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execvp(exePtr.cast<ffi.Char>(), argv);
|
execvp(exePtr.cast<ffi.Char>(), argv);
|
||||||
exit(-2);
|
cExit(-2); // direct libc _exit on exec failure
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PARENT PROCESS BRANCH ---
|
// --- PARENT PROCESS BRANCH ---
|
||||||
malloc.free(pathPtr);
|
malloc.free(pathPtr);
|
||||||
malloc.free(exePtr);
|
malloc.free(exePtr);
|
||||||
|
malloc.free(tmuxNamePtr);
|
||||||
|
malloc.free(tmuxPaneNamePtr);
|
||||||
if (workDirPtr != null) malloc.free(workDirPtr);
|
if (workDirPtr != null) malloc.free(workDirPtr);
|
||||||
malloc.free(argv);
|
malloc.free(argv);
|
||||||
for (final p in allocatedArgs) {
|
for (final p in allocatedArgs) {
|
||||||
@@ -279,16 +296,17 @@ class PtySession {
|
|||||||
final close = libc.lookupFunction<_close_c, _close_dart>('close');
|
final close = libc.lookupFunction<_close_c, _close_dart>('close');
|
||||||
close(masterFd);
|
close(masterFd);
|
||||||
|
|
||||||
|
// Send SIGTERM to terminate child process
|
||||||
final kill = libc.lookupFunction<
|
final kill = libc.lookupFunction<
|
||||||
ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig),
|
ffi.Int32 Function(ffi.Int32 pid, ffi.Int32 sig),
|
||||||
int Function(int pid, int sig)
|
int Function(int pid, int sig)
|
||||||
>('kill');
|
>('kill');
|
||||||
kill(childPid, 15); // SIGTERM = 15
|
kill(childPid, 15); // SIGTERM = 15
|
||||||
|
|
||||||
// Reap child zombie process (non-blocking WNOHANG = 1)
|
// Reap child zombie process (blocking waitpid options = 0 to guarantee reaping)
|
||||||
final waitpid = libc.lookupFunction<_waitpid_c, _waitpid_dart>('waitpid');
|
final waitpid = libc.lookupFunction<_waitpid_c, _waitpid_dart>('waitpid');
|
||||||
final statusPtr = calloc<ffi.Int32>();
|
final statusPtr = calloc<ffi.Int32>();
|
||||||
waitpid(childPid, statusPtr, 1);
|
waitpid(childPid, statusPtr, 0); // Blocking wait guarantees process resource removal
|
||||||
calloc.free(statusPtr);
|
calloc.free(statusPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user