aboutsummaryrefslogtreecommitdiff
path: root/ios-bootstrap/bundle-loader.m
blob: fec2513820498223dab9d59c50276b05077b31bc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
extern char ***_NSGetArgv(void);

#define PREFIX "Substitute bundle loader: "

enum test_filters_ret {
    PASSED,
    FAILED,
    INVALID
};

static double id_to_double(id o) {
    if ([o isKindOfClass:[NSString class]]) {
        NSScanner *scan = [NSScanner scannerWithString:o];
        double d;
        if (![scan scanDouble:&d] || !scan.atEnd)
            return NAN;
        return d;
    } else if ([o isKindOfClass:[NSNumber class]]) {
        return [o doubleValue];
    } else {
        return NAN;
    }
}

static enum test_filters_ret test_filters(NSDictionary *plist_dict) {

    NSDictionary *filter = [plist_dict objectForKey:@"Filter"];
    if (!filter)
        return PASSED;
    if (![filter isKindOfClass:[NSDictionary class]])
        return INVALID;

    for (NSString *key in [filter allKeys]) {
        if (!([key isEqualToString:@"CoreFoundationVersion"] ||
              [key isEqualToString:@"Classes"] ||
              [key isEqualToString:@"Bundles"] ||
              [key isEqualToString:@"Executables"])) {
            return INVALID;
        }
    }
    NSArray *cfv = [filter objectForKey:@"CoreFoundationVersion"];
    if (cfv) {
        if (![cfv isKindOfClass:[NSArray class]] ||
            [cfv count] == 0 ||
            [cfv count] > 2)
            return INVALID;
        double version = kCFCoreFoundationVersionNumber;
        double minimum = id_to_double([cfv objectAtIndex:0]);
        if (minimum != minimum)
            return INVALID;
        if (version < minimum)
            return FAILED;
        id supremum_o = [cfv objectAtIndex:1];
        if (supremum_o) {
            double supremum = id_to_double(supremum_o);
            if (supremum != supremum)
                return INVALID;
            if (version >= supremum)
                return FAILED;
        }
    }

    NSArray *classes = [filter objectForKey:@"Classes"];
    if (classes) {
        if (![classes isKindOfClass:[NSArray class]])
            return INVALID;
        for (NSString *name in classes) {
            if (![name isKindOfClass:[NSString class]])
                return INVALID;
            if (NSClassFromString(name))
                goto ok1;
        }
        return FAILED;
    ok1:;
    }

    NSArray *bundles = [filter objectForKey:@"Bundles"];
    if (bundles) {
        if (![bundles isKindOfClass:[NSArray class]])
            return INVALID;
        for (NSString *identifier in bundles) {
            if (![identifier isKindOfClass:[NSString class]])
                return INVALID;
            if ([NSBundle bundleWithIdentifier:identifier])
                goto ok2;
        }
        return FAILED;
    ok2:;
    }


    NSArray *executables = [filter objectForKey:@"Executables"];
    if (executables) {
        const char *argv0 = (*_NSGetArgv())[0];
        NSString *exe = nil;
        if (argv0) {
            NSString *nsargv0 = [NSString stringWithCString:argv0
                                          encoding:NSUTF8StringEncoding];
            exe = [[nsargv0 pathComponents] lastObject];
        }
        if (!exe)
            exe = @"";
        if (![executables isKindOfClass:[NSArray class]])
            return INVALID;
        for (NSString *name in executables) {
            if (![name isKindOfClass:[NSString class]])
                return INVALID;
            if ([name isEqualToString:exe])
                goto ok3;
        }
        return FAILED;
    ok3:;
    }
    return PASSED;
}

/* this is DYLD_INSERT_LIBRARIES'd, not injected. */
__attribute__((constructor))
static void init() {
    NSError *err;
    NSString *base = @"/Library/Substitute/DynamicLibraries";
    NSArray *list = [[NSFileManager defaultManager]
                     contentsOfDirectoryAtPath:base
                     error:&err];
    if (!list)
        return;

    for (NSString *dylib in list) {
        if (![[dylib pathExtension] isEqualToString:@"dylib"])
            continue;
        NSString *plist = [[dylib stringByDeletingPathExtension]
                           stringByAppendingPathExtension:@"plist"];
        NSString *full_plist = [base stringByAppendingPathComponent:plist];
        NSDictionary *plist_dict = [NSDictionary dictionaryWithContentsOfFile:
                                    full_plist];
        if (!plist_dict) {
            NSLog(@PREFIX "missing, unreadable, or invalid plist '%@' for dylib '%@'; unlike Substrate, we require plists", full_plist, dylib);
            continue;
        }
        enum test_filters_ret ret = test_filters(plist_dict);
        if (ret == FAILED) {
            continue;
        } else if (ret == INVALID) {
            NSLog(@PREFIX "bad data in plist '%@' for dylib '%@'", full_plist, dylib);
            continue;
        }
        NSString *full_dylib = [base stringByAppendingPathComponent:dylib];
        const char *c_dylib = [full_dylib cStringUsingEncoding:NSUTF8StringEncoding];
        if (!c_dylib) {
            NSLog(@PREFIX "Not loading weird dylib path %@", full_dylib);
            continue;
        }
        NSLog(@"Substitute loading %@", full_dylib);
        if (!dlopen(c_dylib, RTLD_LAZY)) {
            NSLog(@PREFIX "Failed to load %@: %s", full_dylib, dlerror());
        }
    }
}