mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
710 lines
20 KiB
Go
710 lines
20 KiB
Go
package xrefs
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type mockMemory struct {
|
|
ptrs map[uint64]uint64
|
|
raws map[uint64]uint64
|
|
strs map[uint64]string
|
|
ptrSlide uint64
|
|
}
|
|
|
|
func (m mockMemory) ReadPointer(addr uint64) (uint64, error) {
|
|
ptr, ok := m.ptrs[addr]
|
|
if !ok {
|
|
return 0, fmt.Errorf("no pointer at %#x", addr)
|
|
}
|
|
if m.ptrSlide != 0 && ptr != 0 {
|
|
ptr += m.ptrSlide
|
|
}
|
|
return ptr, nil
|
|
}
|
|
|
|
func (m mockMemory) ReadUint64(addr uint64) (uint64, error) {
|
|
raw, ok := m.raws[addr]
|
|
if ok {
|
|
return raw, nil
|
|
}
|
|
raw, ok = m.ptrs[addr]
|
|
if !ok {
|
|
return 0, fmt.Errorf("no uint64 at %#x", addr)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
func (m mockMemory) ReadCString(addr uint64) (string, error) {
|
|
str, ok := m.strs[addr]
|
|
if !ok {
|
|
return "", fmt.Errorf("no cstring at %#x", addr)
|
|
}
|
|
return str, nil
|
|
}
|
|
|
|
func TestKernelIOUserClientTargetsSplitBySignature(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
symbol string
|
|
wantOK bool
|
|
wantCanon string
|
|
wantKey int
|
|
}{
|
|
{
|
|
name: "singular qualified",
|
|
symbol: "IOUserClient::copyClientEntitlement(task_t, char const*)",
|
|
wantOK: true,
|
|
wantCanon: "IOUserClient::copyClientEntitlement",
|
|
wantKey: 1,
|
|
},
|
|
{
|
|
name: "singular short",
|
|
symbol: "copyClientEntitlement",
|
|
wantOK: true,
|
|
wantCanon: "IOUserClient::copyClientEntitlement",
|
|
wantKey: 1,
|
|
},
|
|
{
|
|
name: "singular mangled",
|
|
symbol: "__ZN12IOUserClient21copyClientEntitlementEP4taskPKc",
|
|
wantOK: true,
|
|
wantCanon: "IOUserClient::copyClientEntitlement",
|
|
wantKey: 1,
|
|
},
|
|
{
|
|
name: "vnode",
|
|
symbol: "IOUserClient::copyClientEntitlementVnode(vnode*, task_t, char const*)",
|
|
wantOK: true,
|
|
wantCanon: "IOUserClient::copyClientEntitlementVnode",
|
|
wantKey: 2,
|
|
},
|
|
{
|
|
name: "short alias does not match other classes",
|
|
symbol: "OtherUserClient::copyClientEntitlement(task_t, char const*)",
|
|
},
|
|
{
|
|
name: "plural has no single key",
|
|
symbol: "IOUserClient::copyClientEntitlements(task_t, OSArray*)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
target, ok := matchTarget(SourceKernelcache, tt.symbol)
|
|
if ok != tt.wantOK {
|
|
t.Fatalf("ok=%t, want %t", ok, tt.wantOK)
|
|
}
|
|
if !tt.wantOK {
|
|
return
|
|
}
|
|
if target.Canonical != tt.wantCanon || target.KeyReg != tt.wantKey {
|
|
t.Fatalf("target=%#v, want canonical %q key x%d", target, tt.wantCanon, tt.wantKey)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKernelCFunctionTargetsMatchDemangledSignatures(t *testing.T) {
|
|
target, ok := matchTarget(SourceKernelcache, "IOTaskHasEntitlement(task_t, char const*)")
|
|
if !ok {
|
|
t.Fatal("IOTaskHasEntitlement signature did not match")
|
|
}
|
|
if target.Canonical != "IOTaskHasEntitlement" || target.KeyReg != 1 {
|
|
t.Fatalf("target=%#v, want IOTaskHasEntitlement key x1", target)
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionResolvesADRPAddCString(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100001000)
|
|
keyAddr := uint64(0x100002120)
|
|
data := words(
|
|
encADRP(1, base, keyAddr),
|
|
encADDImm(1, 1, keyAddr&0xfff),
|
|
encBL(base+8, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.iokit.IOSurface",
|
|
callerSymbol: "IOSurfaceRootUserClient::init_check",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceKernelcache, Canonical: "IOTaskHasEntitlement", KeyReg: 1, ValueReg: -1}},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.iosurface.client"}},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if !records[0].Resolved || records[0].Key != "com.apple.private.iosurface.client" || records[0].Extra["slice_notes"] != "" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionRejectsADDRegisterAsImmediate(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100001000)
|
|
keyAddr := uint64(0x100002120)
|
|
data := words(
|
|
encADRP(2, base, keyAddr),
|
|
encADDReg(1, 2, 3),
|
|
encBL(base+8, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.driver.Test",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceKernelcache, Canonical: "IOTaskHasEntitlement", KeyReg: 1, ValueReg: -1}},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.wrong"}},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if records[0].Resolved || records[0].Key != "" || records[0].Extra["slice_notes"] != "indirect" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionResolvesCFString(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100001000)
|
|
cfAddr := uint64(0x100003040)
|
|
strAddr := uint64(0x100004000)
|
|
data := words(
|
|
encADRP(1, base, cfAddr),
|
|
encADDImm(1, 1, cfAddr&0xfff),
|
|
encBL(base+8, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/Frameworks/Security.framework/Security",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceDSC, Canonical: "SecTaskCopyValueForEntitlement", KeyReg: 1, ValueReg: -1}},
|
|
},
|
|
mem: mockMemory{
|
|
ptrs: map[uint64]uint64{cfAddr + 16: strAddr},
|
|
strs: map[uint64]string{strAddr: "com.apple.private.security.allow-untrusted"},
|
|
},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if got := records[0].Key; got != "com.apple.private.security.allow-untrusted" {
|
|
t.Fatalf("key=%q", got)
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionResolvesObjCSelectorValueForEntitlement(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
msgSend := uint64(0x100010000)
|
|
selRef := uint64(0x100002000)
|
|
selStr := uint64(0x100003000)
|
|
keyAddr := uint64(0x100004040)
|
|
target, ok := matchTarget(SourceDSC, "_objc_msgSend")
|
|
if !ok {
|
|
t.Fatal("objc_msgSend target did not match")
|
|
}
|
|
data := words(
|
|
encADRP(1, base, selRef),
|
|
encLDRUnsigned(1, 1, selRef&0xfff),
|
|
encADRP(2, base+8, keyAddr),
|
|
encADDImm(2, 2, keyAddr&0xfff),
|
|
encBL(base+16, msgSend),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/Frameworks/Foundation.framework/Foundation",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
msgSend: {target},
|
|
},
|
|
mem: mockMemory{
|
|
ptrs: map[uint64]uint64{selRef: selStr},
|
|
strs: map[uint64]string{
|
|
selStr: "valueForEntitlement:",
|
|
keyAddr: "com.apple.private.xpc.test",
|
|
},
|
|
},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if records[0].CheckFn != "-[NSXPCConnection valueForEntitlement:]" || records[0].Key != "com.apple.private.xpc.test" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionResolvesObjCValueForEntitlementStub(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
stub := uint64(0x100010000)
|
|
keyAddr := uint64(0x100004040)
|
|
target, ok := matchTarget(SourceDSC, "_objc_msgSend$valueForEntitlement:")
|
|
if !ok {
|
|
t.Fatal("objc_msgSend$valueForEntitlement: target did not match")
|
|
}
|
|
data := words(
|
|
encADRP(2, base, keyAddr),
|
|
encADDImm(2, 2, keyAddr&0xfff),
|
|
encBL(base+8, stub),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/Frameworks/Foundation.framework/Foundation",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
stub: {target},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.xpc.test"}},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if records[0].CheckFn != "-[NSXPCConnection valueForEntitlement:]" || records[0].Key != "com.apple.private.xpc.test" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionBLRThroughRegisterEmitsUnresolvedParam(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100005000)
|
|
data := words(
|
|
encADRP(16, base, targetAddr),
|
|
encADDImm(16, 16, targetAddr&0xfff),
|
|
encBLR(16),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.driver.Test",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceKernelcache, Canonical: "IOTaskHasEntitlement", KeyReg: 1, ValueReg: -1}},
|
|
},
|
|
mem: mockMemory{},
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if records[0].Resolved || records[0].Key != "" || records[0].Extra["slice_notes"] != "param" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionExpandsLiteralCFArrayKeys(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100001000)
|
|
arrayAddr := uint64(0x100002000)
|
|
valuesAddr := uint64(0x100003000)
|
|
keyOne := uint64(0x100004000)
|
|
keyTwo := uint64(0x100005000)
|
|
data := words(
|
|
encADRP(1, base, arrayAddr),
|
|
encADDImm(1, 1, arrayAddr&0xfff),
|
|
encBL(base+8, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/Frameworks/Security.framework/Security",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceDSC, Canonical: "SecTaskCopyValuesForEntitlements", KeyReg: 1, ValueReg: -1, KeyArray: true}},
|
|
},
|
|
mem: mockMemory{
|
|
ptrs: map[uint64]uint64{
|
|
arrayAddr + 16: 2,
|
|
arrayAddr + 24: valuesAddr,
|
|
valuesAddr: keyOne,
|
|
valuesAddr + 8: keyTwo,
|
|
},
|
|
strs: map[uint64]string{
|
|
keyOne: "application-identifier",
|
|
keyTwo: "keychain-access-groups",
|
|
},
|
|
},
|
|
})
|
|
|
|
if len(records) != 2 {
|
|
t.Fatalf("records=%d, want 2", len(records))
|
|
}
|
|
if records[0].Key != "application-identifier" || records[1].Key != "keychain-access-groups" {
|
|
t.Fatalf("unexpected records: %#v", records)
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionExpandsLiteralCFArrayKeysWithSlidPointers(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
targetAddr := uint64(0x100001000)
|
|
arrayAddr := uint64(0x100002000)
|
|
valuesAddr := uint64(0x100003000)
|
|
keyOne := uint64(0x100004000)
|
|
keyTwo := uint64(0x100005000)
|
|
slide := uint64(0x1000000000)
|
|
data := words(
|
|
encADRP(1, base, arrayAddr),
|
|
encADDImm(1, 1, arrayAddr&0xfff),
|
|
encBL(base+8, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/Frameworks/Security.framework/Security",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceDSC, Canonical: "SecTaskCopyValuesForEntitlements", KeyReg: 1, ValueReg: -1, KeyArray: true}},
|
|
},
|
|
mem: mockMemory{
|
|
ptrSlide: slide,
|
|
ptrs: map[uint64]uint64{
|
|
arrayAddr + 24: valuesAddr,
|
|
valuesAddr + slide: keyOne,
|
|
valuesAddr + slide + 8: keyTwo,
|
|
},
|
|
raws: map[uint64]uint64{
|
|
arrayAddr + 16: 2,
|
|
},
|
|
strs: map[uint64]string{
|
|
keyOne + slide: "application-identifier",
|
|
keyTwo + slide: "keychain-access-groups",
|
|
},
|
|
},
|
|
})
|
|
|
|
if len(records) != 2 {
|
|
t.Fatalf("records=%d, want 2: %#v", len(records), records)
|
|
}
|
|
if records[0].Key != "application-identifier" || records[1].Key != "keychain-access-groups" {
|
|
t.Fatalf("unexpected records: %#v", records)
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionExpandsStackBuiltCFArrayKeys(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
arrayStub := uint64(0x100010000)
|
|
claimStub := uint64(0x100011000)
|
|
targetAddr := uint64(0x100012000)
|
|
slotOne := uint64(0x100002000)
|
|
slotTwo := uint64(0x100002008)
|
|
slotThree := uint64(0x100002010)
|
|
cfOne := uint64(0x100003000)
|
|
cfTwo := uint64(0x100003100)
|
|
cfThree := uint64(0x100003200)
|
|
strOne := uint64(0x100004000)
|
|
strTwo := uint64(0x100004100)
|
|
strThree := uint64(0x100004200)
|
|
data := words(
|
|
encADRP(8, base, slotOne),
|
|
encADDImm(8, 8, slotOne&0xfff),
|
|
encLDRUnsigned(8, 8, 0),
|
|
encADRP(9, base+12, slotTwo),
|
|
encADDImm(9, 9, slotTwo&0xfff),
|
|
encLDRUnsigned(9, 9, 0),
|
|
encSTP(8, 9, 31, 0x10),
|
|
encADRP(10, base+28, slotThree),
|
|
encADDImm(10, 10, slotThree&0xfff),
|
|
encLDRUnsigned(10, 10, 0),
|
|
encSTRUnsigned(10, 31, 0x20),
|
|
encADDImm(2, 31, 0x10),
|
|
encMOVZ(3, 3),
|
|
encBL(base+52, arrayStub),
|
|
encBL(base+56, claimStub),
|
|
encMOVReg(19, 0),
|
|
encMOVReg(1, 19),
|
|
encBL(base+68, targetAddr),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceDSC,
|
|
image: "/System/Library/PrivateFrameworks/FileProvider.framework/FileProvider",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{
|
|
targetAddr: {{Source: SourceDSC, Canonical: "SecTaskCopyValuesForEntitlements", KeyReg: 1, ValueReg: -1, KeyArray: true}},
|
|
},
|
|
mem: mockMemory{
|
|
ptrs: map[uint64]uint64{
|
|
slotOne: cfOne,
|
|
slotTwo: cfTwo,
|
|
slotThree: cfThree,
|
|
cfOne + 16: strOne,
|
|
cfTwo + 16: strTwo,
|
|
cfThree + 16: strThree,
|
|
},
|
|
strs: map[uint64]string{
|
|
strOne: "com.apple.private.fileprovider.read",
|
|
strTwo: "com.apple.private.fileprovider.write",
|
|
strThree: "com.apple.private.fileprovider.enumerate",
|
|
},
|
|
},
|
|
})
|
|
|
|
if len(records) != 3 {
|
|
t.Fatalf("records=%d, want 3: %#v", len(records), records)
|
|
}
|
|
want := []string{
|
|
"com.apple.private.fileprovider.read",
|
|
"com.apple.private.fileprovider.write",
|
|
"com.apple.private.fileprovider.enumerate",
|
|
}
|
|
for idx, key := range want {
|
|
if !records[idx].Resolved || records[idx].Key != key {
|
|
t.Fatalf("record[%d]=%#v, want key %q", idx, records[idx], key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionResolvesVirtualSlotEntitlementCall(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
keyAddr := uint64(0x100002120)
|
|
data := words(
|
|
encADRP(2, base, keyAddr),
|
|
encADDImm(2, 2, keyAddr&0xfff),
|
|
encLDRUnsigned(8, 0, 0),
|
|
encLDRUnsigned(8, 8, 0x28),
|
|
encBLR(8),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.iokit.IOUserClientTest",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{},
|
|
virtualSlots: map[int][]targetSpec{
|
|
5: {{Source: SourceKernelcache, Canonical: "IOUserClient::copyClientEntitlement", KeyReg: 2, ValueReg: -1, Discovery: "vtable_slot", VirtualSlot: 5}},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.iokit.test"}},
|
|
allowVirtual: true,
|
|
})
|
|
|
|
if len(records) != 1 {
|
|
t.Fatalf("records=%d, want 1", len(records))
|
|
}
|
|
if records[0].CheckFn != "IOUserClient::copyClientEntitlement" || records[0].Key != "com.apple.private.iokit.test" {
|
|
t.Fatalf("unexpected record: %#v", records[0])
|
|
}
|
|
if records[0].Extra["target_discovery"] != "vtable_slot" {
|
|
t.Fatalf("missing discovery provenance: %#v", records[0])
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionSkipsVirtualSlotWhenCallerIsNotScoped(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
keyAddr := uint64(0x100002120)
|
|
data := words(
|
|
encADRP(2, base, keyAddr),
|
|
encADDImm(2, 2, keyAddr&0xfff),
|
|
encLDRUnsigned(8, 0, 0),
|
|
encLDRUnsigned(8, 8, 0x28),
|
|
encBLR(8),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.iokit.Unrelated",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{},
|
|
virtualSlots: map[int][]targetSpec{
|
|
5: {{Source: SourceKernelcache, Canonical: "IOUserClient::copyClientEntitlement", KeyReg: 2, ValueReg: -1, Discovery: "vtable_slot", VirtualSlot: 5}},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.iokit.test"}},
|
|
})
|
|
|
|
if len(records) != 0 {
|
|
t.Fatalf("records=%d, want 0: %#v", len(records), records)
|
|
}
|
|
}
|
|
|
|
func TestScanFunctionSkipsVirtualSlotWhenReceiverIsNotSelf(t *testing.T) {
|
|
base := uint64(0x100000000)
|
|
keyAddr := uint64(0x100002120)
|
|
data := words(
|
|
encADRP(2, base, keyAddr),
|
|
encADDImm(2, 2, keyAddr&0xfff),
|
|
encLDRUnsigned(8, 19, 0),
|
|
encLDRUnsigned(8, 8, 0x28),
|
|
encBLR(8),
|
|
)
|
|
|
|
records := scanFunction(functionScan{
|
|
source: SourceKernelcache,
|
|
image: "com.apple.iokit.IOUserClientTest",
|
|
data: data,
|
|
start: base,
|
|
targets: map[uint64][]targetSpec{},
|
|
virtualSlots: map[int][]targetSpec{
|
|
5: {{Source: SourceKernelcache, Canonical: "IOUserClient::copyClientEntitlement", KeyReg: 2, ValueReg: -1, Discovery: "vtable_slot", VirtualSlot: 5}},
|
|
},
|
|
mem: mockMemory{strs: map[uint64]string{keyAddr: "com.apple.private.iokit.test"}},
|
|
allowVirtual: true,
|
|
})
|
|
|
|
if len(records) != 0 {
|
|
t.Fatalf("records=%d, want 0: %#v", len(records), records)
|
|
}
|
|
}
|
|
|
|
func TestAddResolvedAddressTargetFiltersSelectorTargets(t *testing.T) {
|
|
global := map[uint64][]targetSpec{
|
|
0x1000: {
|
|
{Source: SourceDSC, Canonical: "xpc_connection_has_entitlement", KeyReg: 1, ValueReg: -1},
|
|
{Source: SourceDSC, Canonical: "-[NSXPCConnection valueForEntitlement:]", KeyReg: 2, ValueReg: -1, Selector: "valueForEntitlement:"},
|
|
},
|
|
}
|
|
|
|
targets := make(map[uint64][]targetSpec)
|
|
if !addResolvedAddressTarget(targets, global, 0x2000, 0x1000, false) {
|
|
t.Fatal("expected non-selector target to be added")
|
|
}
|
|
if len(targets[0x2000]) != 1 || targets[0x2000][0].Canonical != "xpc_connection_has_entitlement" {
|
|
t.Fatalf("unexpected filtered targets: %#v", targets[0x2000])
|
|
}
|
|
|
|
targets = make(map[uint64][]targetSpec)
|
|
if !addResolvedAddressTarget(targets, global, 0x2000, 0x1000, true) {
|
|
t.Fatal("expected selector target to be added")
|
|
}
|
|
if len(targets[0x2000]) != 2 {
|
|
t.Fatalf("targets=%d, want 2", len(targets[0x2000]))
|
|
}
|
|
}
|
|
|
|
func TestSkipIndirectDSCScanKeepsGOTOnlyTargets(t *testing.T) {
|
|
if skipIndirectDSCScan(nil, true) {
|
|
t.Fatal("GOT-resolved targets require indirect BLR scanning")
|
|
}
|
|
if skipIndirectDSCScan(map[uint64][]targetSpec{0x1000: {{Canonical: "SecTaskCopyValueForEntitlement"}}}, false) {
|
|
t.Fatal("bind targets require indirect BLR scanning")
|
|
}
|
|
if !skipIndirectDSCScan(nil, false) {
|
|
t.Fatal("images without indirect target sources should keep the direct-branch prefilter")
|
|
}
|
|
}
|
|
|
|
func TestKernelSymbolMapCandidatesIncludeAncestorOutputDirs(t *testing.T) {
|
|
root := t.TempDir()
|
|
outputDir := filepath.Join(root, "ipsw-symbolicator")
|
|
if err := os.Mkdir(outputDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := filepath.Join(outputDir, "kernelcache.release.iPhone18,1.symbols.json")
|
|
if err := os.WriteFile(want, []byte("{}"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(root, "extract", "23E254__iPhone18,1", "kernelcache.release.iPhone18,1")
|
|
candidates := kernelSymbolMapCandidates(path)
|
|
if slices.Contains(candidates, want) {
|
|
return
|
|
}
|
|
t.Fatalf("candidate %q not found in %#v", want, candidates)
|
|
}
|
|
|
|
func TestWriteJSONLDeterministicOrderingAndKeys(t *testing.T) {
|
|
records := []Record{
|
|
{Source: "kernelcache", Image: "b", Callsite: "0x2", CheckFn: "B", Extra: map[string]string{}},
|
|
{Source: "dsc", Image: "z", Callsite: "0x9", CheckFn: "A", Extra: map[string]string{"slice_notes": "param"}},
|
|
{Source: "dsc", Image: "a", Callsite: "0x1", CheckFn: "A", Extra: map[string]string{"target_discovery": "cstring_convergence"}},
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := WriteJSONL(&buf, records); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
|
if len(lines) != 3 {
|
|
t.Fatalf("lines=%d", len(lines))
|
|
}
|
|
if !strings.Contains(lines[0], `"source":"dsc"`) || !strings.Contains(lines[0], `"image":"a"`) {
|
|
t.Fatalf("records not sorted: %s", buf.String())
|
|
}
|
|
if !strings.HasPrefix(lines[0], `{"callsite":`) ||
|
|
!strings.Contains(lines[0], `"extra":{"target_discovery":"cstring_convergence"},"image":`) ||
|
|
!strings.Contains(lines[1], `"extra":{"slice_notes":"param"},"image":`) {
|
|
t.Fatalf("keys not deterministically ordered: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func words(ws ...uint32) []byte {
|
|
var buf bytes.Buffer
|
|
for _, w := range ws {
|
|
_ = binary.Write(&buf, binary.LittleEndian, w)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func encADRP(rd int, pc, target uint64) uint32 {
|
|
pcPage := pc &^ 0xfff
|
|
targetPage := target &^ 0xfff
|
|
pages := int64(targetPage-pcPage) / 0x1000
|
|
imm := uint32(uint64(pages) & ((1 << 21) - 1))
|
|
immlo := imm & 0x3
|
|
immhi := (imm >> 2) & 0x7ffff
|
|
return 0x90000000 | (immlo << 29) | (immhi << 5) | uint32(rd)
|
|
}
|
|
|
|
func encADDImm(rd, rn int, imm uint64) uint32 {
|
|
return 0x91000000 | (uint32(imm&0xfff) << 10) | (uint32(rn) << 5) | uint32(rd)
|
|
}
|
|
|
|
func encADDReg(rd, rn, rm int) uint32 {
|
|
return 0x8b000000 | (uint32(rm) << 16) | (uint32(rn) << 5) | uint32(rd)
|
|
}
|
|
|
|
func encLDRUnsigned(rt, rn int, imm uint64) uint32 {
|
|
return 0xf9400000 | (uint32((imm/8)&0xfff) << 10) | (uint32(rn) << 5) | uint32(rt)
|
|
}
|
|
|
|
func encSTRUnsigned(rt, rn int, imm uint64) uint32 {
|
|
return 0xf9000000 | (uint32((imm/8)&0xfff) << 10) | (uint32(rn) << 5) | uint32(rt)
|
|
}
|
|
|
|
func encSTP(rt, rt2, rn int, imm uint64) uint32 {
|
|
return 0xa9000000 | (uint32((imm/8)&0x7f) << 15) | (uint32(rt2) << 10) | (uint32(rn) << 5) | uint32(rt)
|
|
}
|
|
|
|
func encMOVZ(rd int, imm uint64) uint32 {
|
|
return 0xd2800000 | (uint32(imm&0xffff) << 5) | uint32(rd)
|
|
}
|
|
|
|
func encMOVReg(rd, rn int) uint32 {
|
|
return 0xaa0003e0 | (uint32(rn) << 16) | uint32(rd)
|
|
}
|
|
|
|
func encBL(pc, target uint64) uint32 {
|
|
off := int64(target-pc) / 4
|
|
return 0x94000000 | (uint32(uint64(off)) & 0x03ffffff)
|
|
}
|
|
|
|
func encBLR(rn int) uint32 {
|
|
return 0xd63f0000 | (uint32(rn) << 5)
|
|
}
|