aboutsummaryrefslogtreecommitdiff
path: root/darwin-bootstrap/substituted-plist-loader.m
blob: 0c8d66f65a603bbce2d1f6ac21d5586f46dbe968 (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
162
163
164
165
166
167
168
169
170
171
172
173
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#include "substituted-messages.h"

enum convert_filters_ret {
    PROVISIONAL_PASS,
    FAIL,
    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 convert_filters_ret convert_filters(NSDictionary *plist_dict,
                                                const char *exec_name,
                                                NSMutableData *test_out) {

    NSDictionary *filter = [plist_dict objectForKey:@"Filter"];
    if (!filter)
        return PROVISIONAL_PASS;
    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;
        }
    }

    /* First do the two we can test here. */

    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 FAIL;
        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 FAIL;
        }
    }

    NSArray *executables = [filter objectForKey:@"Executables"];
    if (executables) {
        NSString *exe = [NSString stringWithCString:exec_name
                                  encoding:NSUTF8StringEncoding];
        if (![executables isKindOfClass:[NSArray class]])
            return INVALID;
        for (NSString *name in executables) {
            if (![name isKindOfClass:[NSString class]])
                return INVALID;
            if ([name isEqualToString:exe])
                goto ok;
        }
        return FAIL;
    ok:;
    }

    /* Convert the rest to substituted_bundle_list_ops. */

    struct {
        __unsafe_unretained NSString *key;
        uint8_t opc;
    } types[2] = {
        {@"Classes", SUBSTITUTED_TEST_CLASS},
        {@"Bundles", SUBSTITUTED_TEST_BUNDLE},
    };

    for (int i = 0; i < 2; i++) {
        NSArray *things = [filter objectForKey:types[i].key];
        if (things) {
            if (![things isKindOfClass:[NSArray class]])
                return INVALID;
            for (NSString *name in things) {
                if (![name isKindOfClass:[NSString class]])
                    return INVALID;
                NSData *name_data = [name dataUsingEncoding:NSUTF8StringEncoding];
                size_t len = [name_data length];
                if (len > 65535)
                    return INVALID;
                struct substituted_bundle_list_op op = {(uint16_t) len,
                                                        types[i].opc};
                [test_out appendBytes:&op length:sizeof(op)];
                [test_out appendData:name_data];
                static char zero = '\0';
                [test_out appendBytes:&zero length:1];
            }
        }
    }

    return PROVISIONAL_PASS;
}

static NSData *the_bundle_list;

void get_bundle_list(const char *exec_name,
                     const void **bundle_list, size_t *bundle_list_size) {
    /* TODO cache */
    NSError *err;
    NSString *base = @"/Library/Substitute/DynamicLibraries";
    NSArray *list = [[NSFileManager defaultManager]
                     contentsOfDirectoryAtPath:base
                     error:&err];
    if (!list)
        return;

    NSMutableData *out = [NSMutableData data];

    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(@"missing, unreadable, or invalid plist '%@' for dylib '%@'; unlike Substrate, we require plists", full_plist, dylib);
            continue;
        }

        NSMutableData *test = [NSMutableData data];
        enum convert_filters_ret ret = convert_filters(plist_dict, exec_name, test);
        if (ret == FAIL) {
            continue;
        } else if (ret == INVALID) {
            NSLog(@"bad data in plist '%@' for dylib '%@'", full_plist, dylib);
            continue;
        }
        NSString *dylib_path = [base stringByAppendingPathComponent:dylib];
        NSData *dylib_path_data = [dylib_path dataUsingEncoding:NSUTF8StringEncoding];
        size_t len = [dylib_path length];
        if (len > 65535) {
            NSLog(@"dylib '%@' somehow has an absurdly long path", dylib);
            continue;
        }
        [out appendData:test];
        struct substituted_bundle_list_op op = {(uint16_t) len,
                                                SUBSTITUTED_USE_DYLIB};
        [out appendBytes:&op length:sizeof(op)];
        [out appendData:dylib_path_data];
        static char zero = '\0';
        [out appendBytes:&zero length:1];
    }
    the_bundle_list = out;
    *bundle_list = [out bytes];
    *bundle_list_size = [out length];
}