Skip to content

Commit 4872daa

Browse files
committed
feat(objc): Detect and reject legacy Objective-C fragile runtime metadata #54
Add runtime detection that distinguishes non-fragile (modern) from fragile (legacy `__OBJC` segment) Objective-C metadata. All ObjC parsing APIs now return ErrObjcFragileRuntimeUnsupported when called on binaries with only legacy metadata, since the existing parser implements only the modern runtime ABI. Binaries with both legacy and modern metadata are allowed through to support transition-era builds.
1 parent 7e27dae commit 4872daa

File tree

4 files changed

+311
-14
lines changed

4 files changed

+311
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This package goes beyond the Go's `debug/macho` to:
1313
- Cover ALL load commands and architectures
1414
- Provide nice summary string output
1515
- Allow for creating custom MachO files
16-
- Parse Objective-C runtime information
16+
- Parse Objective-C runtime information (non-fragile runtime ABI; legacy `__OBJC` metadata is detected and reported unsupported)
1717
- Parse Swift runtime information
1818
- Read/Write code signature information
1919
- Parse fixup chain information

file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type File struct {
5252
swift map[uint64]any
5353
ledata *bytes.Buffer // tmp storage of linkedit data
5454

55+
objcRuntimeOnce sync.Once
56+
objcHasNonFragileRuntime bool
57+
objcHasFragileRuntime bool
58+
5559
sharedCacheRelativeSelectorBaseVMAddress uint64 // objc_opt version 16
5660
swiftAutoDemangle bool
5761

objc.go

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,33 @@ import (
1515

1616
var ErrObjcSectionNotFound = errors.New("missing required ObjC section")
1717
var ErrObjcSectionNEmpty = errors.New("required ObjC section is empty")
18+
var ErrObjcFragileRuntimeUnsupported = errors.New("objective-c fragile runtime metadata is unsupported")
19+
20+
var legacyObjCSectionNames = map[string]struct{}{
21+
"__image_info": {},
22+
"__module_info": {},
23+
"__class": {},
24+
"__meta_class": {},
25+
"__protocol": {},
26+
"__protocol_ext": {},
27+
"__category": {},
28+
"__class_vars": {},
29+
"__instance_vars": {},
30+
"__cls_refs": {},
31+
"__message_refs": {},
32+
"__symbols": {},
33+
"__sel_refs": {},
34+
"__string_object": {},
35+
"__class_names": {},
36+
"__meth_var_names": {},
37+
"__meth_var_types": {},
38+
"__selector_strs": {},
39+
}
40+
41+
func isLegacyObjCSectionName(name string) bool {
42+
_, ok := legacyObjCSectionNames[strings.ToLower(name)]
43+
return ok
44+
}
1845

1946
// TODO refactor into a pkg
2047

@@ -58,21 +85,43 @@ func (f *File) getCStringWithFallback(addr uint64, label string, allowSwift bool
5885
return "", err
5986
}
6087

61-
// HasObjC returns true if MachO contains a __objc_imageinfo section
62-
func (f *File) HasObjC() bool {
63-
for _, s := range f.Segments() {
64-
if strings.HasPrefix(s.Name, "__DATA") {
65-
if sec := f.Section(s.Name, "__objc_imageinfo"); sec != nil {
66-
return true
88+
func (f *File) hasObjCNonFragileRuntime() bool {
89+
f.detectObjCRuntimeKinds()
90+
return f.objcHasNonFragileRuntime
91+
}
92+
93+
func (f *File) hasObjCFragileRuntime() bool {
94+
f.detectObjCRuntimeKinds()
95+
return f.objcHasFragileRuntime
96+
}
97+
98+
func (f *File) detectObjCRuntimeKinds() {
99+
f.objcRuntimeOnce.Do(func() {
100+
for _, sec := range f.Sections {
101+
if strings.HasPrefix(sec.Seg, "__DATA") && strings.HasPrefix(sec.Name, "__objc_") {
102+
f.objcHasNonFragileRuntime = true
103+
}
104+
if strings.EqualFold(sec.Seg, "__OBJC") && isLegacyObjCSectionName(sec.Name) {
105+
f.objcHasFragileRuntime = true
106+
}
107+
if f.objcHasNonFragileRuntime && f.objcHasFragileRuntime {
108+
return
67109
}
68110
}
111+
})
112+
}
113+
114+
func (f *File) ensureObjCNonFragileRuntime(api string) error {
115+
f.detectObjCRuntimeKinds()
116+
if f.objcHasFragileRuntime && !f.objcHasNonFragileRuntime {
117+
return fmt.Errorf("%s requires the Objective-C non-fragile runtime: %w", api, ErrObjcFragileRuntimeUnsupported)
69118
}
70-
if f.CPU == types.CPUI386 {
71-
if sec := f.Section("__OBJC", "__image_info"); sec != nil {
72-
return true
73-
}
74-
}
75-
return false
119+
return nil
120+
}
121+
122+
// HasObjC returns true if MachO contains Objective-C metadata.
123+
func (f *File) HasObjC() bool {
124+
return f.hasObjCNonFragileRuntime() || f.hasObjCFragileRuntime()
76125
}
77126

78127
// HasPlusLoadMethod returns true if MachO contains a __objc_nlclslist or __objc_nlcatlist section
@@ -100,6 +149,9 @@ func (f *File) HasObjCMessageReferences() bool {
100149
}
101150
}
102151
}
152+
if sec := f.Section("__OBJC", "__message_refs"); sec != nil {
153+
return true
154+
}
103155
return false
104156
}
105157

@@ -126,7 +178,7 @@ func (f *File) GetObjCToc() objc.Toc {
126178
case "__objc_selrefs":
127179
oInfo.SelRefs = sec.Size / f.pointerSize()
128180
}
129-
} else if (f.CPU == types.CPUI386) && strings.EqualFold(sec.Name, "__OBJC") {
181+
} else if strings.EqualFold(sec.Seg, "__OBJC") {
130182
if strings.EqualFold(sec.Name, "__message_refs") {
131183
oInfo.SelRefs += sec.SectionHeader.Size / 4
132184
} else if strings.EqualFold(sec.Name, "__class") {
@@ -144,6 +196,10 @@ func (f *File) GetObjCToc() objc.Toc {
144196

145197
// GetObjCImageInfo returns the parsed __objc_imageinfo data
146198
func (f *File) GetObjCImageInfo() (*objc.ImageInfo, error) {
199+
if err := f.ensureObjCNonFragileRuntime("GetObjCImageInfo"); err != nil {
200+
return nil, err
201+
}
202+
147203
var imgInfo objc.ImageInfo
148204
for _, s := range f.Segments() {
149205
if strings.HasPrefix(s.Name, "__DATA") {
@@ -196,6 +252,10 @@ func (f *File) GetObjCClassInfo(vmaddr uint64) (*objc.ClassRO64, error) {
196252

197253
// GetObjCClassNames returns a map of section data virtual memory address to their class names
198254
func (f *File) GetObjCClassNames() (map[uint64]string, error) {
255+
if err := f.ensureObjCNonFragileRuntime("GetObjCClassNames"); err != nil {
256+
return nil, err
257+
}
258+
199259
class2vmaddr := make(map[uint64]string)
200260

201261
if sec := f.Section("__TEXT", "__objc_classname"); sec != nil { // Names for locally implemented classes
@@ -230,6 +290,10 @@ func (f *File) GetObjCClassNames() (map[uint64]string, error) {
230290

231291
// GetObjCMethodNames returns a map of section data virtual memory addresses to their method names
232292
func (f *File) GetObjCMethodNames() (map[uint64]string, error) {
293+
if err := f.ensureObjCNonFragileRuntime("GetObjCMethodNames"); err != nil {
294+
return nil, err
295+
}
296+
233297
meth2vmaddr := make(map[uint64]string)
234298

235299
if sec := f.Section("__TEXT", "__objc_methname"); sec != nil { // Method names for locally implemented methods
@@ -264,6 +328,10 @@ func (f *File) GetObjCMethodNames() (map[uint64]string, error) {
264328

265329
// GetObjCClasses returns an array of Objective-C classes
266330
func (f *File) GetObjCClasses() ([]objc.Class, error) {
331+
if err := f.ensureObjCNonFragileRuntime("GetObjCClasses"); err != nil {
332+
return nil, err
333+
}
334+
267335
var classes []objc.Class
268336

269337
for _, s := range f.Segments() {
@@ -313,6 +381,10 @@ func (f *File) GetObjCClasses() ([]objc.Class, error) {
313381

314382
// GetObjCNonLazyClasses returns an array of Objective-C classes that implement +load
315383
func (f *File) GetObjCNonLazyClasses() ([]objc.Class, error) {
384+
if err := f.ensureObjCNonFragileRuntime("GetObjCNonLazyClasses"); err != nil {
385+
return nil, err
386+
}
387+
316388
var classes []objc.Class
317389

318390
for _, s := range f.Segments() {
@@ -381,6 +453,9 @@ func (f *File) disablePreattachedCategories(vmaddr uint64) (uint64, error) {
381453

382454
// GetObjCClass parses an Objective-C class at a given virtual memory address
383455
func (f *File) GetObjCClass(vmaddr uint64) (*objc.Class, error) {
456+
if err := f.ensureObjCNonFragileRuntime("GetObjCClass"); err != nil {
457+
return nil, err
458+
}
384459

385460
if c, ok := f.GetObjC(vmaddr); ok {
386461
return c.(*objc.Class), nil
@@ -572,6 +647,9 @@ func (f *File) GetObjCClass(vmaddr uint64) (*objc.Class, error) {
572647
// TODO: get rid of old GetObjCClass
573648
// GetObjCClass parses an Objective-C class at a given virtual memory address
574649
func (f *File) GetObjCClass2(vmaddr uint64) (*objc.Class, error) {
650+
if err := f.ensureObjCNonFragileRuntime("GetObjCClass2"); err != nil {
651+
return nil, err
652+
}
575653

576654
if c, ok := f.GetObjC(vmaddr); ok {
577655
return c.(*objc.Class), nil
@@ -763,6 +841,10 @@ func (f *File) GetObjCClass2(vmaddr uint64) (*objc.Class, error) {
763841

764842
// GetObjCCategories returns an array of Objective-C categories by parsing the __objc_catlist data
765843
func (f *File) GetObjCCategories() ([]objc.Category, error) {
844+
if err := f.ensureObjCNonFragileRuntime("GetObjCCategories"); err != nil {
845+
return nil, err
846+
}
847+
766848
var categoryPtr objc.CategoryT
767849
var categories []objc.Category
768850

@@ -880,6 +962,10 @@ func (f *File) GetObjCCategories() ([]objc.Category, error) {
880962

881963
// GetObjCNonLazyCategories returns an array of Objective-C classes that implement +load
882964
func (f *File) GetObjCNonLazyCategories() ([]objc.Category, error) {
965+
if err := f.ensureObjCNonFragileRuntime("GetObjCNonLazyCategories"); err != nil {
966+
return nil, err
967+
}
968+
883969
var cats []objc.Category
884970

885971
for _, s := range f.Segments() {
@@ -1099,6 +1185,9 @@ func (f *File) getObjcProtocol(vmaddr uint64) (proto *objc.Protocol, err error)
10991185

11001186
// GetObjCProtocols returns the Objective-C protocols
11011187
func (f *File) GetObjCProtocols() ([]objc.Protocol, error) {
1188+
if err := f.ensureObjCNonFragileRuntime("GetObjCProtocols"); err != nil {
1189+
return nil, err
1190+
}
11021191

11031192
var protocols []objc.Protocol
11041193

@@ -1133,6 +1222,10 @@ func (f *File) GetObjCProtocols() ([]objc.Protocol, error) {
11331222
}
11341223

11351224
func (f *File) GetObjCMethods(vmaddr uint64) ([]objc.Method, error) {
1225+
if err := f.ensureObjCNonFragileRuntime("GetObjCMethods"); err != nil {
1226+
return nil, err
1227+
}
1228+
11361229
if c, ok := f.GetObjC(vmaddr); ok {
11371230
return c.([]objc.Method), nil
11381231
}
@@ -1152,6 +1245,10 @@ func (f *File) GetObjCMethods(vmaddr uint64) ([]objc.Method, error) {
11521245

11531246
// GetObjCMethodLists parses the method lists in the __objc_methlist section
11541247
func (f *File) GetObjCMethodLists() ([]objc.Method, error) {
1248+
if err := f.ensureObjCNonFragileRuntime("GetObjCMethodLists"); err != nil {
1249+
return nil, err
1250+
}
1251+
11551252
var methods []objc.Method
11561253
var methodList objc.MethodList
11571254
var nextMethodListOffset uint64
@@ -1310,6 +1407,10 @@ func (f *File) forEachObjCMethod(methodListVMAddr uint64, handler func(uint64, o
13101407

13111408
// GetObjCIvars returns the Objective-C instance variables
13121409
func (f *File) GetObjCIvars(vmaddr uint64) ([]objc.Ivar, error) {
1410+
if err := f.ensureObjCNonFragileRuntime("GetObjCIvars"); err != nil {
1411+
return nil, err
1412+
}
1413+
13131414
return f.getObjCIvarsWithSwift(vmaddr, false)
13141415
}
13151416

@@ -1394,6 +1495,10 @@ func (f *File) getObjCIvarsWithSwift(vmaddr uint64, allowSwift bool) ([]objc.Iva
13941495

13951496
// GetObjCProperties returns the Objective-C properties
13961497
func (f *File) GetObjCProperties(vmaddr uint64) ([]objc.Property, error) {
1498+
if err := f.ensureObjCNonFragileRuntime("GetObjCProperties"); err != nil {
1499+
return nil, err
1500+
}
1501+
13971502
return f.getObjCPropertiesWithSwift(vmaddr, false)
13981503
}
13991504

@@ -1439,6 +1544,10 @@ func (f *File) getObjCPropertiesWithSwift(vmaddr uint64, allowSwift bool) ([]obj
14391544

14401545
// GetObjCClassReferences returns a map of classes to their section data virtual memory address
14411546
func (f *File) GetObjCClassReferences() (map[uint64]*objc.Class, error) {
1547+
if err := f.ensureObjCNonFragileRuntime("GetObjCClassReferences"); err != nil {
1548+
return nil, err
1549+
}
1550+
14421551
clsRefs := make(map[uint64]*objc.Class)
14431552

14441553
for _, s := range f.Segments() {
@@ -1486,6 +1595,10 @@ func (f *File) GetObjCClassReferences() (map[uint64]*objc.Class, error) {
14861595

14871596
// GetObjCSuperReferences returns a map of super classes to their section data virtual memory address
14881597
func (f *File) GetObjCSuperReferences() (map[uint64]*objc.Class, error) {
1598+
if err := f.ensureObjCNonFragileRuntime("GetObjCSuperReferences"); err != nil {
1599+
return nil, err
1600+
}
1601+
14891602
clsRefs := make(map[uint64]*objc.Class)
14901603

14911604
for _, s := range f.Segments() {
@@ -1532,6 +1645,10 @@ func (f *File) GetObjCSuperReferences() (map[uint64]*objc.Class, error) {
15321645

15331646
// GetObjCProtoReferences returns a map of protocol names to their section data virtual memory address
15341647
func (f *File) GetObjCProtoReferences() (map[uint64]*objc.Protocol, error) {
1648+
if err := f.ensureObjCNonFragileRuntime("GetObjCProtoReferences"); err != nil {
1649+
return nil, err
1650+
}
1651+
15351652
protRefs := make(map[uint64]*objc.Protocol)
15361653

15371654
for _, s := range f.Segments() {
@@ -1569,6 +1686,10 @@ func (f *File) GetObjCProtoReferences() (map[uint64]*objc.Protocol, error) {
15691686

15701687
// GetObjCSelectorReferences returns a map of selector names to their section data virtual memory address
15711688
func (f *File) GetObjCSelectorReferences() (map[uint64]*objc.Selector, error) {
1689+
if err := f.ensureObjCNonFragileRuntime("GetObjCSelectorReferences"); err != nil {
1690+
return nil, err
1691+
}
1692+
15721693
selRefs := make(map[uint64]*objc.Selector)
15731694

15741695
for _, s := range f.Segments() {

0 commit comments

Comments
 (0)