:: limine / common / lib / uri.c 17.6 KB raw

1
#include <stdint.h>
2
#include <stddef.h>
3
#include <lib/uri.h>
4
#include <lib/misc.h>
5
#include <lib/part.h>
6
#include <lib/libc.h>
7
#include <lib/config.h>
8
#include <fs/file.h>
9
#include <mm/pmm.h>
10
#include <lib/print.h>
11
#include <pxe/tftp.h>
12
#include <menu.h>
13
#include <lib/getchar.h>
14
#include <crypt/blake2b.h>
15
#include <compress/gzip.h>
16
17
// A URI takes the form of: resource(root):/path#hash
18
// The following function splits up a URI into its components.
19
// Note: Returns pointers into a static buffer. Callers must copy values
20
// if they need to persist across multiple uri_resolve() calls.
21
bool uri_resolve(char *uri, char **resource, char **root, char **path, char **hash) {
22
    #define URI_BUF_SIZE 4096
23
    static char buf[URI_BUF_SIZE];
24
25
    size_t length = strlen(uri) + 1;
26
    if (length > URI_BUF_SIZE) {
27
        panic(true, "uri_resolve: URI too long (max %u)", URI_BUF_SIZE - 1);
28
    }
29
    memcpy(buf, uri, length);
30
    uri = buf;
31
32
    *resource = *root = *path = *hash = NULL;
33
34
    // Get resource
35
    for (size_t i = 0; ; i++) {
36
        if (strlen(uri + i) < 1)
37
            return false;
38
39
        if (!strncmp(uri + i, "(", 1)) {
40
            *resource = uri;
41
            uri[i] = 0;
42
            uri += i + 1;
43
            break;
44
        }
45
    }
46
47
    // Get root
48
    for (size_t i = 0; ; i++) {
49
        if (strlen(uri + i) < 3)
50
            return false;
51
52
        if (!strncmp(uri + i, "):/", 3)) {
53
            *root = uri;
54
            uri[i] = 0;
55
            uri += i + 3;
56
            break;
57
        }
58
    }
59
60
    // Get path
61
    if (*uri == 0)
62
        return false;
63
    *path = uri;
64
65
    // Get hash
66
    for (int i = (int)strlen(uri) - 1; i >= 0; i--) {
67
        if (uri[i] != '#') {
68
            continue;
69
        }
70
71
        uri[i++] = 0;
72
73
        if (hash != NULL) {
74
            *hash = uri + i;
75
        }
76
77
        if (strlen(uri + i) != 128) {
78
            panic(true, "Blake2b hash must be 128 characters long");
79
            return false;
80
        }
81
82
        // Validate all 128 characters are valid hexadecimal
83
        for (size_t j = 0; j < 128; j++) {
84
            char c = uri[i + j];
85
            if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
86
                panic(true, "Blake2b hash contains invalid character at position %d", (int)j);
87
                return false;
88
            }
89
        }
90
91
        break;
92
    }
93
94
    return true;
95
}
96
97
static bool parse_bios_partition(char *loc, int *drive, int *partition) {
98
    uint64_t val;
99
100
    for (size_t i = 0; ; i++) {
101
        if (loc[i] == 0)
102
            return false;
103
104
        if (loc[i] == ':') {
105
            loc[i] = 0;
106
            if (*loc == 0) {
107
                panic(true, "Drive number cannot be omitted for hdd():/ and odd():/");
108
            } else {
109
                val = strtoui(loc, NULL, 10);
110
                if (val < 1 || val > 256) {
111
                    panic(true, "Drive number outside range 1-256");
112
                }
113
                *drive = val;
114
            }
115
            loc += i + 1;
116
            break;
117
        }
118
    }
119
120
    val = strtoui(loc, NULL, 10);
121
    if (val > 256) {
122
        panic(true, "Partition number outside range 0-256");
123
    }
124
    *partition = val;
125
126
    return true;
127
}
128
129
static struct file_handle *uri_hdd_dispatch(char *loc, char *path) {
130
    int drive, partition;
131
132
    if (!parse_bios_partition(loc, &drive, &partition))
133
        return NULL;
134
135
    struct volume *volume = volume_get_by_coord(false, drive, partition);
136
137
    if (volume == NULL)
138
        return NULL;
139
140
    return fopen(volume, path);
141
}
142
143
static struct file_handle *uri_odd_dispatch(char *loc, char *path) {
144
    int drive, partition;
145
146
    if (!parse_bios_partition(loc, &drive, &partition))
147
        return NULL;
148
149
    struct volume *volume = volume_get_by_coord(true, drive, partition);
150
151
    if (volume == NULL)
152
        return NULL;
153
154
    return fopen(volume, path);
155
}
156
157
static struct file_handle *uri_guid_dispatch(char *guid_str, char *path) {
158
    struct guid guid;
159
    if (!string_to_guid_be(&guid, guid_str))
160
        return NULL;
161
162
    struct volume *volume = volume_get_by_guid(&guid);
163
    if (volume == NULL) {
164
        if (!string_to_guid_mixed(&guid, guid_str))
165
            return NULL;
166
167
        volume = volume_get_by_guid(&guid);
168
        if (volume == NULL)
169
            return NULL;
170
    }
171
172
    return fopen(volume, path);
173
}
174
175
static struct file_handle *uri_fslabel_dispatch(char *fslabel, char *path) {
176
    struct volume *volume = volume_get_by_fslabel(fslabel);
177
    if (volume == NULL) {
178
        return NULL;
179
    }
180
181
    return fopen(volume, path);
182
}
183
184
static struct file_handle *uri_tftp_dispatch(char *root, char *path) {
185
    uint32_t ip;
186
    if (!strcmp(root, "")) {
187
        ip = 0;
188
    } else {
189
        if (inet_pton(root, &ip)) {
190
            panic(true, "tftp: Invalid ipv4 address: %s", root);
191
        }
192
    }
193
194
    struct file_handle *ret;
195
    if ((ret = tftp_open(boot_volume, root, path)) == NULL) {
196
        return NULL;
197
    }
198
199
    return ret;
200
}
201
202
static struct file_handle *uri_boot_dispatch(char *s_part, char *path) {
203
    if (boot_volume->pxe)
204
        return uri_tftp_dispatch(s_part, path);
205
206
    int partition;
207
208
    if (s_part[0] != '\0') {
209
        uint64_t val = strtoui(s_part, NULL, 10);
210
        if (val > 256) {
211
            panic(true, "Partition number outside range 0-256");
212
        }
213
        partition = val;
214
    } else {
215
        partition = boot_volume->partition;
216
    }
217
218
    struct volume *volume = volume_get_by_coord(boot_volume->is_optical,
219
                                                boot_volume->index, partition);
220
    if (volume == NULL)
221
        return NULL;
222
223
    return fopen(volume, path);
224
}
225
226
// Release a range of memory previously reserved with memmap_alloc_range.
227
// Works for both low and high addresses, unlike pmm_free which truncates
228
// on 32-bit builds.
229
static void uri_release_range(uint64_t addr, uint64_t count) {
230
    count = ALIGN_UP(count, 4096, panic(false, "uri: alignment overflow"));
231
    memmap_alloc_range(addr, count, MEMMAP_USABLE, 0, false, false, true);
232
}
233
234
// Allocate `count` bytes via ext_mem_alloc_type_aligned_mode and return
235
// the physical address in *out_addr. When allow_high_mem is true on i386
236
// and the allocator landed above 4 GiB, *out_low is set to NULL and the
237
// 64-bit address is stored in *out_addr. Otherwise *out_low points at the
238
// allocation and *out_addr == (uintptr_t)*out_low.
239
static void uri_alloc(uint64_t count, uint32_t type, bool allow_high_mem,
240
                      void **out_low, uint64_t *out_addr) {
241
    void *ret = ext_mem_alloc_type_aligned_mode(count, type, 4096, allow_high_mem);
242
#if defined (__i386__)
243
    if (allow_high_mem) {
244
        uint64_t addr = *(uint64_t *)ret;
245
        if (addr >= 0x100000000) {
246
            *out_low = NULL;
247
            *out_addr = addr;
248
            return;
249
        }
250
        ret = (void *)(uintptr_t)addr;
251
    }
252
#else
253
    (void)allow_high_mem;
254
#endif
255
    *out_low = ret;
256
    *out_addr = (uintptr_t)ret;
257
}
258
259
struct file_handle *uri_open(char *uri, uint32_t type, bool allow_high_mem
260
#if defined (__i386__)
261
    , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count)
262
    , void (*memcpy_from_64)(void *dst, uint64_t src, size_t count)
263
#endif
264
) {
265
#if defined (__i386__)
266
    if (memcpy_to_64 == NULL || memcpy_from_64 == NULL) {
267
        allow_high_mem = false;
268
    }
269
#endif
270
271
    struct file_handle *raw;
272
273
    char *resource = NULL, *root = NULL, *path = NULL, *hash = NULL;
274
    if (!uri_resolve(uri, &resource, &root, &path, &hash)) {
275
        return NULL;
276
    }
277
278
    if (resource == NULL) {
279
        panic(true, "No resource specified for URI `%#`.", uri);
280
    }
281
282
    bool gz_compressed = *resource == '$';
283
    if (gz_compressed) {
284
        resource++;
285
    }
286
287
    if (!strcmp(resource, "hdd")) {
288
        raw = uri_hdd_dispatch(root, path);
289
    } else if (!strcmp(resource, "odd")) {
290
        raw = uri_odd_dispatch(root, path);
291
    } else if (!strcmp(resource, "boot")) {
292
        raw = uri_boot_dispatch(root, path);
293
    } else if (!strcmp(resource, "guid")) {
294
        raw = uri_guid_dispatch(root, path);
295
    } else if (!strcmp(resource, "uuid")) {
296
        raw = uri_guid_dispatch(root, path);
297
    } else if (!strcmp(resource, "fslabel")) {
298
        raw = uri_fslabel_dispatch(root, path);
299
    } else if (!strcmp(resource, "tftp")) {
300
        raw = uri_tftp_dispatch(root, path);
301
    } else {
302
        panic(true, "Resource `%s` not valid.", resource);
303
    }
304
305
    if (raw == NULL) {
306
        return NULL;
307
    }
308
309
    if (secure_boot_active && hash == NULL) {
310
        panic(true, "Secure Boot is active and URI `%#` has no associated hash!", uri);
311
    }
312
313
    uint8_t hash_buf[BLAKE2B_OUT_BYTES];
314
    if (hash != NULL) {
315
        for (size_t i = 0; i < sizeof(hash_buf); i++) {
316
            hash_buf[i] = digit_to_int(hash[i * 2]) << 4 | digit_to_int(hash[i * 2 + 1]);
317
        }
318
    }
319
320
    // Snapshot metadata from raw before the close cascade frees its buffers.
321
    struct volume *raw_vol = raw->vol;
322
    size_t raw_path_len = raw->path_len;
323
    char *raw_path_copy = NULL;
324
    if (raw->path != NULL && raw_path_len > 0) {
325
        raw_path_copy = ext_mem_alloc(raw_path_len);
326
        memcpy(raw_path_copy, raw->path, raw_path_len);
327
    }
328
#if defined (UEFI)
329
    EFI_HANDLE raw_efi_part = raw->efi_part_handle;
330
#endif
331
    bool raw_pxe = raw->pxe;
332
    uint32_t raw_pxe_ip = raw->pxe_ip;
333
    uint16_t raw_pxe_port = raw->pxe_port;
334
335
    // Build the filter chain: raw -> blake2b -> gzip. blake2b hashes on-disk
336
    // (compressed) bytes.
337
    struct file_handle *top = raw;
338
    struct file_handle *hash_fh = NULL;
339
    if (hash != NULL) {
340
        hash_fh = blake2b_open(top);
341
        top = hash_fh;
342
    }
343
    if (gz_compressed) {
344
        top = gzip_open(top);
345
    }
346
347
    // Drain the stream into a final allocation.
348
    void *buf_low = NULL;
349
    uint64_t buf_addr = 0;
350
    uint64_t buf_cap = 0;
351
    uint64_t buf_len = 0;
352
    bool is_high = false;
353
354
    if (!gz_compressed) {
355
        // Size is authoritative. Single up-front allocation, one copy.
356
        uint64_t sz = top->size;
357
        uri_alloc(sz, type, allow_high_mem, &buf_low, &buf_addr);
358
        is_high = (buf_low == NULL);
359
360
#if defined (__i386__)
361
        if (is_high) {
362
            // 1 MiB bounce loop, same as the old freadall_mode high path.
363
            void *pool = ext_mem_alloc(0x100000);
364
            for (uint64_t i = 0; i < sz; i += 0x100000) {
365
                size_t chunk = sz - i < 0x100000 ? (size_t)(sz - i) : 0x100000;
366
                uint64_t got = top->read(top, pool, i, chunk);
367
                if (got != chunk) {
368
                    panic(false, "uri: short read from non-gzip stream");
369
                }
370
                memcpy_to_64(buf_addr + i, pool, chunk);
371
            }
372
            pmm_free(pool, 0x100000);
373
        } else
374
#endif
375
        {
376
            // In-place fill.
377
            if (sz > 0) {
378
                uint64_t got = top->read(top, buf_low, 0, sz);
379
                if (got != sz) {
380
                    panic(false, "uri: short read from non-gzip stream");
381
                }
382
            }
383
        }
384
        buf_len = sz;
385
    } else {
386
        // Size is unknown (UINT64_MAX from gzip_open). Stretchy vector.
387
        // Initial capacity: 1 MiB, doubles on exhaustion.
388
        buf_cap = 0x100000;
389
        uri_alloc(buf_cap, type, allow_high_mem, &buf_low, &buf_addr);
390
        is_high = (buf_low == NULL);
391
392
#if defined (__i386__)
393
        // High-path uses a 1 MiB bounce pool for both the read side and
394
        // the grow-copy; reused across iterations.
395
        void *pool = is_high ? ext_mem_alloc(0x100000) : NULL;
396
#endif
397
398
        for (;;) {
399
            if (buf_len == buf_cap) {
400
                // Grow: double up to 64 MiB, then add 64 MiB per step.
401
                // Doubling past that wastes too much memory on large files.
402
                uint64_t new_cap = buf_cap < 0x4000000
403
                    ? buf_cap * 2
404
                    : buf_cap + 0x4000000;
405
                uint64_t delta = new_cap - buf_cap;
406
407
                // Try to extend in place by claiming the USABLE range
408
                // immediately below the current buffer. The allocator is
409
                // top-down, so above is already taken; below is the only
410
                // direction that can be contiguous. On success we only
411
                // pay delta extra bytes, not 2x peak.
412
                if (buf_addr >= delta &&
413
                    memmap_alloc_range(buf_addr - delta, delta, type,
414
                                       MEMMAP_USABLE, false, false, false)) {
415
                    uint64_t base = buf_addr - delta;
416
                    // Move existing data down. dest < src, forward-safe.
417
#if defined (__i386__)
418
                    if (is_high) {
419
                        for (uint64_t off = 0; off < buf_len; off += 0x100000) {
420
                            size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000;
421
                            memcpy_from_64(pool, buf_addr + off, chunk);
422
                            memcpy_to_64(base + off, pool, chunk);
423
                        }
424
                    } else
425
#endif
426
                    {
427
                        memmove((void *)(uintptr_t)base, buf_low, buf_len);
428
                        buf_low = (void *)(uintptr_t)base;
429
                    }
430
                    buf_addr = base;
431
                    buf_cap = new_cap;
432
                    goto grew;
433
                }
434
435
                void *new_low = NULL;
436
                uint64_t new_addr = 0;
437
                uri_alloc(new_cap, type, allow_high_mem, &new_low, &new_addr);
438
                bool new_is_high = (new_low == NULL);
439
440
#if defined (__i386__)
441
                if (is_high && new_is_high) {
442
                    // 64-to-64: bounce via low pool in 1 MiB strides.
443
                    for (uint64_t off = 0; off < buf_len; off += 0x100000) {
444
                        size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000;
445
                        memcpy_from_64(pool, buf_addr + off, chunk);
446
                        memcpy_to_64(new_addr + off, pool, chunk);
447
                    }
448
                } else if (is_high && !new_is_high) {
449
                    // Shouldn't happen: once we landed high we ask for high.
450
                    // Keep a defensive path: bounce via pool, then memcpy.
451
                    for (uint64_t off = 0; off < buf_len; off += 0x100000) {
452
                        size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000;
453
                        memcpy_from_64(pool, buf_addr + off, chunk);
454
                        memcpy((uint8_t *)new_low + off, pool, chunk);
455
                    }
456
                } else if (!is_high && new_is_high) {
457
                    for (uint64_t off = 0; off < buf_len; off += 0x100000) {
458
                        size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000;
459
                        memcpy_to_64(new_addr + off, (uint8_t *)buf_low + off, chunk);
460
                    }
461
                } else
462
#endif
463
                {
464
                    (void)new_is_high;   /*  Silence unused warning on non-i386.  */
465
                    memcpy(new_low, buf_low, buf_len);
466
                }
467
468
                // Release the old allocation.
469
                uri_release_range(buf_addr, buf_cap);
470
471
                buf_low = new_low;
472
                buf_addr = new_addr;
473
                buf_cap = new_cap;
474
#if defined (__i386__)
475
                if (is_high != new_is_high && new_is_high && pool == NULL) {
476
                    pool = ext_mem_alloc(0x100000);
477
                }
478
                is_high = new_is_high;
479
#endif
480
grew:;
481
            }
482
483
            uint64_t want = buf_cap - buf_len;
484
            if (want > 65536) want = 65536;
485
486
            uint64_t got;
487
#if defined (__i386__)
488
            if (is_high) {
489
                got = top->read(top, pool, buf_len, want);
490
                if (got > 0) memcpy_to_64(buf_addr + buf_len, pool, got);
491
            } else
492
#endif
493
            {
494
                got = top->read(top, (uint8_t *)buf_low + buf_len, buf_len, want);
495
            }
496
            if (got == 0) break;
497
            buf_len += got;
498
        }
499
500
        // Release the page-aligned tail past the actual data so we don't
501
        // hand the OS up to 64 MiB of slack typed as `type`. Keep at least
502
        // one page so the returned handle has a valid address.
503
        uint64_t kept = ALIGN_UP(buf_len, 4096, panic(true, "uri: alignment overflow"));
504
        if (kept == 0) kept = 4096;
505
        if (kept < buf_cap) {
506
            uri_release_range(buf_addr + kept, buf_cap - kept);
507
            buf_cap = kept;
508
        }
509
510
#if defined (__i386__)
511
        if (pool != NULL) pmm_free(pool, 0x100000);
512
#endif
513
    }
514
515
    // Finalize hash check now that all compressed bytes have flowed through
516
    // the filter.
517
    if (hash_fh != NULL) {
518
        if (!blake2b_check_hash(hash_fh, hash_buf)) {
519
            if (hash_mismatch_panic) {
520
                panic(true, "Blake2b hash for URI `%#` does not match!", uri);
521
            } else {
522
                print("WARNING: Blake2b hash for URI `%#` does not match!\n"
523
                      "         Press Y to continue, press any other key to return to menu...", uri);
524
525
                char ch = getchar();
526
                if (ch != 'Y' && ch != 'y') {
527
                    menu(false);
528
                }
529
                print("\n");
530
            }
531
        }
532
    }
533
534
    // Close the filter chain. fclose cascades.
535
    fclose(top);
536
537
    // Build the returned memfile. Fresh allocation so we never mutate any
538
    // closed filter handle's state.
539
    struct file_handle *out = ext_mem_alloc(sizeof(struct file_handle));
540
    out->is_memfile = true;
541
    out->readall = true;
542
    out->is_high_mem = is_high;
543
    out->fd = is_high ? NULL : buf_low;
544
    out->load_addr_64 = buf_addr;
545
    out->size = buf_len;
546
    out->vol = raw_vol;
547
    out->path = raw_path_copy;
548
    out->path_len = raw_path_copy != NULL ? raw_path_len : 0;
549
#if defined (UEFI)
550
    out->efi_part_handle = raw_efi_part;
551
#endif
552
    out->pxe = raw_pxe;
553
    out->pxe_ip = raw_pxe_ip;
554
    out->pxe_port = raw_pxe_port;
555
556
    return out;
557
}
tab: 248 wrap: offon