fix macOS PTY: cmsghdr layout, signing, sandbox removal
macOS cmsghdr uses socklen_t (4 bytes) for cmsg_len, not size_t (8 bytes like Linux). The Dart FFI struct was reading at wrong offsets causing SCM_RIGHTS fd transfer to fail. Added CmsghdrDarwin with Uint32 cmsg_len alongside the existing Linux layout. SOL_SOCKET is 0xffff on macOS (was hardcoded to 1 for Linux). Now platform-detected. Switched from ad-hoc to Apple Development signing with team ID. Removed app sandbox from entitlements — proper signing makes it unnecessary. All SBPL hacks eliminated. PtySession.spawn() runs recvFd in a child isolate to avoid blocking the backend isolate's event loop. Terminal testmode test now passes: pane.spawn via backend isolate successfully forks and execs the shell. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
45e8132a41
commit
243a92798c
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"exported_at": "2026-04-26T13:55:29Z",
|
||||
"exported_at": "2026-04-26T15:04:22Z",
|
||||
"decisions": [
|
||||
{
|
||||
"id": "D-1",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:ffi' as ffi;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:ffi/ffi.dart' as pkg_ffi;
|
||||
|
||||
@@ -21,8 +22,8 @@ import 'package:ffi/ffi.dart' as pkg_ffi;
|
||||
const int afUnix = 1;
|
||||
const int sockStream = 1;
|
||||
|
||||
const int solSocket = 1; // Linux; macOS = 0xffff
|
||||
const int scmRights = 1;
|
||||
final int solSocket = Platform.isMacOS ? 0xffff : 1;
|
||||
final int scmRights = Platform.isMacOS ? 0x01 : 1;
|
||||
|
||||
const int fIoNonblock = 0x800; // O_NONBLOCK — 04000 octal
|
||||
const int fGetFl = 3;
|
||||
@@ -133,7 +134,10 @@ final class Msghdr extends ffi.Struct {
|
||||
|
||||
/// POSIX `struct cmsghdr` prefix. We treat the rest of the control
|
||||
/// buffer as a raw byte region and compute offsets by hand.
|
||||
final class Cmsghdr extends ffi.Struct {
|
||||
// On Linux, cmsg_len is size_t (8 bytes on 64-bit).
|
||||
// On macOS, cmsg_len is socklen_t (4 bytes, always).
|
||||
// Use platform-specific structs.
|
||||
final class CmsghdrLinux extends ffi.Struct {
|
||||
@ffi.IntPtr()
|
||||
external int cmsg_len;
|
||||
@ffi.Int32()
|
||||
@@ -142,6 +146,18 @@ final class Cmsghdr extends ffi.Struct {
|
||||
external int cmsg_type;
|
||||
}
|
||||
|
||||
final class CmsghdrDarwin extends ffi.Struct {
|
||||
@ffi.Uint32()
|
||||
external int cmsg_len;
|
||||
@ffi.Int32()
|
||||
external int cmsg_level;
|
||||
@ffi.Int32()
|
||||
external int cmsg_type;
|
||||
}
|
||||
|
||||
// Alias for backward compatibility — callers use Cmsghdr.
|
||||
typedef Cmsghdr = CmsghdrLinux;
|
||||
|
||||
/// POSIX `struct winsize` for `TIOCSWINSZ`.
|
||||
final class Winsize extends ffi.Struct {
|
||||
@ffi.Uint16()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:ffi' as ffi;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:ffi/ffi.dart' as pkg_ffi;
|
||||
|
||||
@@ -58,21 +59,28 @@ int recvFd(int socketFd) {
|
||||
);
|
||||
}
|
||||
|
||||
// Parse the first cmsghdr out of the control buffer. We assume a
|
||||
// single SCM_RIGHTS cmsg with one int of payload — that's what
|
||||
// `ptyc` sends and all we ever ask for.
|
||||
final hdr = control.cast<libc.Cmsghdr>().ref;
|
||||
if (hdr.cmsg_level != libc.solSocket || hdr.cmsg_type != libc.scmRights) {
|
||||
// Parse the first cmsghdr out of the control buffer. On macOS,
|
||||
// cmsg_len is socklen_t (4 bytes); on Linux it's size_t (8 bytes).
|
||||
int cmsgLevel, cmsgType, dataOffset;
|
||||
if (Platform.isMacOS) {
|
||||
final hdr = control.cast<libc.CmsghdrDarwin>().ref;
|
||||
cmsgLevel = hdr.cmsg_level;
|
||||
cmsgType = hdr.cmsg_type;
|
||||
dataOffset = ffi.sizeOf<libc.CmsghdrDarwin>();
|
||||
} else {
|
||||
final hdr = control.cast<libc.CmsghdrLinux>().ref;
|
||||
cmsgLevel = hdr.cmsg_level;
|
||||
cmsgType = hdr.cmsg_type;
|
||||
dataOffset = ffi.sizeOf<libc.CmsghdrLinux>();
|
||||
}
|
||||
|
||||
if (cmsgLevel != libc.solSocket || cmsgType != libc.scmRights) {
|
||||
throw PtyException(
|
||||
'recvmsg',
|
||||
'unexpected cmsg level=${hdr.cmsg_level} type=${hdr.cmsg_type}',
|
||||
'unexpected cmsg level=$cmsgLevel type=$cmsgType',
|
||||
);
|
||||
}
|
||||
|
||||
// CMSG_DATA starts at the first aligned boundary after the cmsghdr.
|
||||
// On Linux/glibc that's sizeof(cmsghdr) == 16, which is 8-byte
|
||||
// aligned already. We rely on that layout.
|
||||
final dataOffset = ffi.sizeOf<libc.Cmsghdr>();
|
||||
final fdPtr = (control + dataOffset).cast<ffi.Int32>();
|
||||
return fdPtr.value;
|
||||
} finally {
|
||||
|
||||
@@ -525,7 +525,8 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
@@ -601,7 +602,8 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
@@ -657,7 +659,8 @@
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
|
||||
@@ -2,30 +2,9 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.temporary-exception.sbpl</key>
|
||||
<array>
|
||||
<string>(allow process-exec* (literal "/bin/zsh"))</string>
|
||||
<string>(allow process-exec* (literal "/usr/bin/which"))</string>
|
||||
<string>(allow process-exec* (subpath "/Users/jeroenschweitzer/.local/bin"))</string>
|
||||
<string>(allow process-exec* (subpath "/Users/jeroenschweitzer/Projects/clide/ptyc/bin"))</string>
|
||||
<string>(allow process-exec* (subpath "/Users/jeroenschweitzer/Projects/clide/native/dugite"))</string>
|
||||
<string>(allow process-fork)</string>
|
||||
<string>(allow file-read* file-write* (subpath "/Users/jeroenschweitzer/Projects"))</string>
|
||||
<string>(allow file-read* file-write* (subpath "/Users/jeroenschweitzer/projects"))</string>
|
||||
<string>(allow file-read* (subpath "/opt/homebrew"))</string>
|
||||
<string>(allow file-read* (subpath "/Users/jeroenschweitzer/.local"))</string>
|
||||
<string>(allow file-read* (subpath "/Users/jeroenschweitzer/.pql"))</string>
|
||||
<string>(allow file-read* file-write* (subpath "/private/tmp"))</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Reference in New Issue
Block a user