detached 898d276

fix(platform): correct darwin process ABI and argv bounds

commit
898d27698c07d3e4b690d7d879e3125318901724
author
brice · · 2026-07-16 18:17 UTC
parent
fe5ad84
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +37 -2
M internal/platform/process.go +6 -1
@@ -36,7 +36,7 @@ func parseKernProcArgs2(data []byte) ([]string, error) {
3636 if len(data) < argcSize {
3737 return nil, fmt.Errorf("KERN_PROCARGS2: truncated argc")
3838 }
39- argc := int(binary.LittleEndian.Uint32(data[:argcSize]))
39+ argc := int(int32(binary.LittleEndian.Uint32(data[:argcSize])))
4040 if argc <= 0 {
4141 return nil, fmt.Errorf("KERN_PROCARGS2: invalid argc %d", argc)
4242 }
@@ -50,6 +50,11 @@ func parseKernProcArgs2(data []byte) ([]string, error) {
5050 for len(rest) > 0 && rest[0] == 0 {
5151 rest = rest[1:]
5252 }
53+ // Each argument must contain at least its terminating NUL byte. Check this
54+ // before allocating from the kernel-provided argc.
55+ if argc > len(rest) {
56+ return nil, fmt.Errorf("KERN_PROCARGS2: argc %d exceeds remaining data", argc)
57+ }
5358
5459 argv := make([]string, 0, argc)
5560 for len(argv) < argc {
@@ -36,7 +36,7 @@ func parseKernProcArgs2(data []byte) ([]string, error) {
36 if len(data) < argcSize {36 if len(data) < argcSize {
37 return nil, fmt.Errorf("KERN_PROCARGS2: truncated argc")37 return nil, fmt.Errorf("KERN_PROCARGS2: truncated argc")
38 }38 }
39- argc := int(binary.LittleEndian.Uint32(data[:argcSize]))39+ argc := int(int32(binary.LittleEndian.Uint32(data[:argcSize])))
40 if argc <= 0 {40 if argc <= 0 {
41 return nil, fmt.Errorf("KERN_PROCARGS2: invalid argc %d", argc)41 return nil, fmt.Errorf("KERN_PROCARGS2: invalid argc %d", argc)
42 }42 }
@@ -50,6 +50,11 @@ func parseKernProcArgs2(data []byte) ([]string, error) {
50 for len(rest) > 0 && rest[0] == 0 {50 for len(rest) > 0 && rest[0] == 0 {
51 rest = rest[1:]51 rest = rest[1:]
52 }52 }
53+ // Each argument must contain at least its terminating NUL byte. Check this
54+ // before allocating from the kernel-provided argc.
55+ if argc > len(rest) {
56+ return nil, fmt.Errorf("KERN_PROCARGS2: argc %d exceeds remaining data", argc)
57+ }
53 58
54 argv := make([]string, 0, argc)59 argv := make([]string, 0, argc)
55 for len(argv) < argc {60 for len(argv) < argc {
M internal/platform/process_darwin_arm64.go +0 -1
@@ -238,7 +238,6 @@ type externProc struct {
238238 Un [16]byte
239239 Vmspace uintptr
240240 Sigacts uintptr
241- _ [3]byte
242241 Flag int32
243242 Stat int8
244243 Pid int32
@@ -238,7 +238,6 @@ type externProc struct {
238 Un [16]byte238 Un [16]byte
239 Vmspace uintptr239 Vmspace uintptr
240 Sigacts uintptr240 Sigacts uintptr
241- _ [3]byte
242 Flag int32241 Flag int32
243 Stat int8242 Stat int8
244 Pid int32243 Pid int32
M internal/platform/process_darwin_arm64_test.go +21 -0
@@ -6,8 +6,29 @@ import (
66 "os"
77 "syscall"
88 "testing"
9+ "unsafe"
910 )
1011
12+func TestDarwinKinfoProcLayout(t *testing.T) {
13+ tests := []struct {
14+ name string
15+ got uintptr
16+ want uintptr
17+ }{
18+ {"Proc.Pid", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Pid), 40},
19+ {"Proc.Comm", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Comm), 243},
20+ {"Eproc.Pgid", unsafe.Offsetof(kinfoProc{}.Eproc) + unsafe.Offsetof(eproc{}.Pgid), 564},
21+ {"kinfoProc size", unsafe.Sizeof(kinfoProc{}), darwinKinfoProcSize},
22+ }
23+ for _, tt := range tests {
24+ t.Run(tt.name, func(t *testing.T) {
25+ if tt.got != tt.want {
26+ t.Fatalf("%s = %d; want %d", tt.name, tt.got, tt.want)
27+ }
28+ })
29+ }
30+}
31+
1132 func TestDarwinProcessInspectorCurrentProcess(t *testing.T) {
1233 inspector := NewProcessInspector()
1334 pid := os.Getpid()
@@ -6,8 +6,29 @@ import (
6 "os"6 "os"
7 "syscall"7 "syscall"
8 "testing"8 "testing"
9+ "unsafe"
9 )10 )
10 11
12+func TestDarwinKinfoProcLayout(t *testing.T) {
13+ tests := []struct {
14+ name string
15+ got uintptr
16+ want uintptr
17+ }{
18+ {"Proc.Pid", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Pid), 40},
19+ {"Proc.Comm", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Comm), 243},
20+ {"Eproc.Pgid", unsafe.Offsetof(kinfoProc{}.Eproc) + unsafe.Offsetof(eproc{}.Pgid), 564},
21+ {"kinfoProc size", unsafe.Sizeof(kinfoProc{}), darwinKinfoProcSize},
22+ }
23+ for _, tt := range tests {
24+ t.Run(tt.name, func(t *testing.T) {
25+ if tt.got != tt.want {
26+ t.Fatalf("%s = %d; want %d", tt.name, tt.got, tt.want)
27+ }
28+ })
29+ }
30+}
31+
11 func TestDarwinProcessInspectorCurrentProcess(t *testing.T) {32 func TestDarwinProcessInspectorCurrentProcess(t *testing.T) {
12 inspector := NewProcessInspector()33 inspector := NewProcessInspector()
13 pid := os.Getpid()34 pid := os.Getpid()
M internal/platform/process_linux_test.go +10 -0
@@ -174,6 +174,16 @@ func TestParseKernProcArgs2(t *testing.T) {
174174 data: append([]byte{1, 0, 0, 0}, []byte("/bin/tool\x00tool")...),
175175 wantErr: true,
176176 },
177+ {
178+ name: "negative argc",
179+ data: append([]byte{0xff, 0xff, 0xff, 0xff}, []byte("/bin/tool\x00tool\x00")...),
180+ wantErr: true,
181+ },
182+ {
183+ name: "argc exceeds remaining data",
184+ data: append([]byte{0xff, 0xff, 0xff, 0x7f}, []byte("/bin/tool\x00tool\x00")...),
185+ wantErr: true,
186+ },
177187 }
178188
179189 for _, tt := range tests {
@@ -174,6 +174,16 @@ func TestParseKernProcArgs2(t *testing.T) {
174 data: append([]byte{1, 0, 0, 0}, []byte("/bin/tool\x00tool")...),174 data: append([]byte{1, 0, 0, 0}, []byte("/bin/tool\x00tool")...),
175 wantErr: true,175 wantErr: true,
176 },176 },
177+ {
178+ name: "negative argc",
179+ data: append([]byte{0xff, 0xff, 0xff, 0xff}, []byte("/bin/tool\x00tool\x00")...),
180+ wantErr: true,
181+ },
182+ {
183+ name: "argc exceeds remaining data",
184+ data: append([]byte{0xff, 0xff, 0xff, 0x7f}, []byte("/bin/tool\x00tool\x00")...),
185+ wantErr: true,
186+ },
177 }187 }
178 188
179 for _, tt := range tests {189 for _, tt := range tests {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close