:: limine / common / fs / iso9660.s2.c 18.4 KB raw

1
#include <stdint.h>
2
#include <stddef.h>
3
#include <fs/iso9660.h>
4
#include <lib/misc.h>
5
#include <lib/libc.h>
6
#include <mm/pmm.h>
7
8
#define ISO9660_SECTOR_SIZE (2 << 10)
9
10
struct iso9660_context {
11
    struct volume *vol;
12
    void *root;
13
    uint32_t root_size;
14
};
15
16
struct iso9660_extent {
17
    uint32_t LBA;
18
    uint32_t size;
19
};
20
21
struct iso9660_file_handle {
22
    struct iso9660_context *context;
23
    uint64_t total_size;
24
    uint32_t extent_count;
25
    struct iso9660_extent *extents;
26
};
27
28
#define ISO9660_FLAG_DIRECTORY    0x02
29
#define ISO9660_FLAG_MULTI_EXTENT 0x80
30
31
#define ISO9660_FIRST_VOLUME_DESCRIPTOR 0x10
32
#define ISO9660_VOLUME_DESCRIPTOR_SIZE ISO9660_SECTOR_SIZE
33
#define ROCK_RIDGE_MAX_FILENAME 255
34
#define ISO9660_MAX_EXTENT_COUNT 65536
35
36
// --- Both endian structures ---
37
struct BE16_t { uint16_t little, big; } __attribute__((packed));
38
struct BE32_t { uint32_t little, big; } __attribute__((packed));
39
40
// --- Directory entries ---
41
struct iso9660_directory_entry {
42
    uint8_t length;
43
    uint8_t extended_attribute_length;
44
    struct BE32_t extent;
45
    struct BE32_t extent_size;
46
    uint8_t datetime[7];
47
    uint8_t flags;
48
    uint8_t interleaved_unit_size;
49
    uint8_t interleaved_gap_size;
50
    struct BE16_t volume_seq;
51
    uint8_t filename_size;
52
    char name[];
53
} __attribute__((packed));
54
55
// --- Volume descriptors ---
56
// VDT = Volume Descriptor Type
57
enum {
58
    ISO9660_VDT_BOOT_RECORD,
59
    ISO9660_VDT_PRIMARY,
60
    ISO9660_VDT_SUPPLEMENTARY,
61
    ISO9660_VDT_PARTITION_DESCRIPTOR,
62
    ISO9660_VDT_TERMINATOR = 255
63
};
64
65
struct iso9660_volume_descriptor {
66
    uint8_t type;
67
    char identifier[5];
68
    uint8_t version;
69
} __attribute__((packed));
70
71
struct iso9660_primary_volume {
72
    struct iso9660_volume_descriptor volume_descriptor;
73
74
    union {
75
        struct {
76
            uint8_t unused0[1];
77
            char system_identifier[32];
78
            char volume_identifier[32];
79
            uint8_t unused1[8];
80
            struct BE32_t space_size;
81
            uint8_t unused2[32];
82
            struct BE16_t set_size;
83
            struct BE16_t volume_seq;
84
            struct BE16_t LBA_size;
85
            struct BE32_t path_table_size;
86
87
            uint32_t LBA_path_table_little;
88
            uint32_t LBA_optional_path_table_little;
89
            uint32_t LBA_path_table_big;
90
            uint32_t LBA_optional_path_table_big;
91
92
            struct iso9660_directory_entry root;
93
        } __attribute__((packed));
94
95
        uint8_t padding[2041];
96
    };
97
} __attribute__((packed));
98
99
100
// --- Implementation ---
101
struct iso9660_contexts_node {
102
    struct iso9660_context context;
103
    struct iso9660_contexts_node *next;
104
};
105
106
static struct iso9660_contexts_node *contexts = NULL;
107
108
// Maximum number of volume descriptors to scan before giving up
109
#define ISO9660_MAX_VOLUME_DESCRIPTORS 256
110
111
// Maximum directory size to prevent memory exhaustion (64MB)
112
#define ISO9660_MAX_DIR_SIZE (64 * 1024 * 1024)
113
114
static void iso9660_find_PVD(struct iso9660_primary_volume *desc, struct volume *vol) {
115
    uint32_t lba = ISO9660_FIRST_VOLUME_DESCRIPTOR;
116
    uint32_t max_lba = ISO9660_FIRST_VOLUME_DESCRIPTOR + ISO9660_MAX_VOLUME_DESCRIPTORS;
117
118
    while (lba < max_lba) {
119
        uint64_t offset = (uint64_t)lba * ISO9660_SECTOR_SIZE;
120
        if (!volume_read(vol, desc, offset, sizeof(struct iso9660_primary_volume))) {
121
            panic(false, "ISO9660: failed to read volume descriptor");
122
        }
123
124
        switch (desc->volume_descriptor.type) {
125
        case ISO9660_VDT_PRIMARY:
126
            return;
127
        case ISO9660_VDT_TERMINATOR:
128
            panic(false, "ISO9660: no primary volume descriptor");
129
            break;
130
        }
131
132
        ++lba;
133
    }
134
135
    panic(false, "ISO9660: exceeded maximum volume descriptor search limit");
136
}
137
138
static void iso9660_cache_root(struct volume *vol,
139
                               void **root,
140
                               uint32_t *root_size) {
141
    struct iso9660_primary_volume pv;
142
    iso9660_find_PVD(&pv, vol);
143
144
    *root_size = pv.root.extent_size.little;
145
146
    // Validate root directory size to prevent memory exhaustion, and require
147
    // sector alignment so directory-traversal sector-skip arithmetic is sound.
148
    if (*root_size == 0 || *root_size > ISO9660_MAX_DIR_SIZE
149
     || *root_size % ISO9660_SECTOR_SIZE != 0) {
150
        panic(false, "ISO9660: Invalid root directory size");
151
    }
152
153
    *root = ext_mem_alloc(*root_size);
154
    uint64_t offset = (uint64_t)pv.root.extent.little * ISO9660_SECTOR_SIZE;
155
    if (!volume_read(vol, *root, offset, *root_size)) {
156
        panic(false, "ISO9660: failed to read root directory");
157
    }
158
}
159
160
static struct iso9660_context *iso9660_get_context(struct volume *vol) {
161
    struct iso9660_contexts_node *current = contexts;
162
    while (current) {
163
        if (current->context.vol == vol)
164
            return &current->context;
165
        current = current->next;
166
    }
167
168
    // The context is not cached at this point
169
    struct iso9660_contexts_node *node = ext_mem_alloc(sizeof(struct iso9660_contexts_node));
170
    node->context.vol = vol;
171
    iso9660_cache_root(vol, &node->context.root, &node->context.root_size);
172
173
    node->next = contexts;
174
    contexts = node;
175
    return &node->context;
176
}
177
178
static bool load_name(char *buf, size_t limit, struct iso9660_directory_entry *entry) {
179
    // Validate entry->length is large enough
180
    if (entry->length < sizeof(struct iso9660_directory_entry) + entry->filename_size) {
181
        goto use_iso_name;
182
    }
183
184
    unsigned char* sysarea = ((unsigned char*)entry) + sizeof(struct iso9660_directory_entry) + entry->filename_size;
185
    size_t sysarea_len = entry->length - sizeof(struct iso9660_directory_entry) - entry->filename_size;
186
    if ((entry->filename_size & 0x1) == 0) {
187
        if (sysarea_len == 0) {
188
            goto use_iso_name;
189
        }
190
        sysarea++;
191
        sysarea_len--;
192
    }
193
194
    // Accumulate Rock Ridge name from possibly multiple NM entries
195
    size_t name_len = 0;
196
    bool found_nm = false;
197
    while ((sysarea_len >= 4) && (sysarea[3] == 1)) {
198
        if (sysarea[2] > sysarea_len || sysarea[2] == 0) {
199
            break;
200
        }
201
        if (sysarea[0] == 'N' && sysarea[1] == 'M' && sysarea[2] >= 5) {
202
            size_t frag_len = sysarea[2] - 5;
203
            if (name_len + frag_len >= limit) {
204
                panic(false, "iso9660: Filename size exceeded");
205
            }
206
            memcpy(buf + name_len, sysarea + 5, frag_len);
207
            name_len += frag_len;
208
            found_nm = true;
209
210
            // Check CONTINUE flag (bit 0 of flags byte at offset 4)
211
            if (!(sysarea[4] & 1)) {
212
                break;
213
            }
214
        }
215
        sysarea_len -= sysarea[2];
216
        sysarea += sysarea[2];
217
    }
218
219
    if (found_nm) {
220
        buf[name_len] = 0;
221
        return true;
222
    }
223
224
use_iso_name:
225
    name_len = entry->filename_size;
226
    if (name_len >= limit) {
227
        panic(false, "iso9660: Filename size exceeded");
228
    }
229
    // Validate that entry->length can actually hold the filename
230
    // entry->length must be >= sizeof(struct) + filename_size for safe access
231
    if (entry->length < sizeof(struct iso9660_directory_entry) + name_len) {
232
        // Corrupted entry: claimed filename_size exceeds actual entry data
233
        // Clamp name_len to what's actually available
234
        if (entry->length <= sizeof(struct iso9660_directory_entry)) {
235
            name_len = 0;
236
        } else {
237
            name_len = entry->length - sizeof(struct iso9660_directory_entry);
238
        }
239
    }
240
    size_t j;
241
    for (j = 0; j < name_len; j++) {
242
        if (entry->name[j] == ';')
243
            break;
244
        if (entry->name[j] == '.' && j + 1 < name_len && entry->name[j+1] == ';')
245
            break;
246
        buf[j] = entry->name[j];
247
    }
248
    buf[j] = 0;
249
    return false;
250
}
251
252
// Advance to the next directory entry in the buffer
253
// Returns NULL if no more entries or invalid entry
254
static struct iso9660_directory_entry *iso9660_next_entry(void *current, void *buffer_end) {
255
    struct iso9660_directory_entry *entry = current;
256
257
    if (entry->length == 0) {
258
        // Skip to next sector boundary
259
        uintptr_t current_addr = (uintptr_t)current;
260
        uintptr_t next_sector = ALIGN_UP(current_addr + 1, ISO9660_SECTOR_SIZE, return NULL);
261
        if (next_sector >= (uintptr_t)buffer_end)
262
            return NULL;
263
        entry = (struct iso9660_directory_entry *)next_sector;
264
        if (entry->length == 0)
265
            return NULL;
266
        return entry;
267
    }
268
269
    void *next = (uint8_t *)current + entry->length;
270
    if (next >= buffer_end)
271
        return NULL;
272
273
    entry = next;
274
275
    // Handle zero-length entries (padding at sector boundaries)
276
    if (entry->length == 0) {
277
        uintptr_t next_sector = ALIGN_UP((uintptr_t)next + 1, ISO9660_SECTOR_SIZE, return NULL);
278
        if (next_sector >= (uintptr_t)buffer_end)
279
            return NULL;
280
        entry = (struct iso9660_directory_entry *)next_sector;
281
        if (entry->length == 0)
282
            return NULL;
283
    }
284
285
    // Validate minimum entry size
286
    if (entry->length < sizeof(struct iso9660_directory_entry))
287
        return NULL;
288
289
    // Validate that the entire entry (as declared by its length field) is
290
    // within the buffer, so callers can safely read all entry->length bytes.
291
    if ((size_t)entry->length > (size_t)((uint8_t *)buffer_end - (uint8_t *)entry))
292
        return NULL;
293
294
    return entry;
295
}
296
297
static struct iso9660_directory_entry *iso9660_find(void *buffer, uint32_t size, const char *filename) {
298
    while (size) {
299
        struct iso9660_directory_entry *entry = buffer;
300
301
        if (entry->length == 0) {
302
            if (size <= ISO9660_SECTOR_SIZE)
303
                return NULL;
304
            size_t prev_size = size;
305
            size = ALIGN_DOWN(size, ISO9660_SECTOR_SIZE);
306
            // If size didn't change (was already aligned), force move to next sector
307
            if (prev_size == size) {
308
                if (size <= ISO9660_SECTOR_SIZE)
309
                    return NULL;
310
                size -= ISO9660_SECTOR_SIZE;
311
                buffer += ISO9660_SECTOR_SIZE;
312
            } else {
313
                buffer += prev_size - size;
314
            }
315
            continue;
316
        }
317
318
        // Validate entry->length doesn't exceed remaining buffer
319
        if (entry->length > size) {
320
            return NULL;  // Corrupted directory entry
321
        }
322
323
        // Minimum valid directory entry size
324
        if (entry->length < sizeof(struct iso9660_directory_entry)) {
325
            return NULL;  // Corrupted directory entry
326
        }
327
328
        char entry_filename[256];
329
        bool rr = load_name(entry_filename, 256, entry);
330
331
        if (rr && !case_insensitive_fopen) {
332
            if (strcmp(filename, entry_filename) == 0) {
333
                return buffer;
334
            }
335
        } else {
336
            if (strcasecmp(filename, entry_filename) == 0) {
337
                return buffer;
338
            }
339
        }
340
341
        size -= entry->length;
342
        buffer += entry->length;
343
    }
344
345
    return NULL;
346
}
347
348
static uint64_t iso9660_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count);
349
static void iso9660_close(struct file_handle *file);
350
351
struct file_handle *iso9660_open(struct volume *vol, const char *path) {
352
    char buf[6];
353
    const uint64_t signature = ISO9660_FIRST_VOLUME_DESCRIPTOR * ISO9660_SECTOR_SIZE + 1;
354
    if (!volume_read(vol, buf, signature, 5)) {
355
        return NULL;
356
    }
357
    buf[5] = '\0';
358
    if (strcmp(buf, "CD001") != 0) {
359
        return NULL;
360
    }
361
362
    struct iso9660_file_handle *ret = ext_mem_alloc(sizeof(struct iso9660_file_handle));
363
364
    ret->context = iso9660_get_context(vol);
365
366
    while (*path == '/')
367
        ++path;
368
369
    struct iso9660_directory_entry *current = ret->context->root;
370
    uint32_t current_size = ret->context->root_size;
371
372
    bool first = true;
373
374
    uint32_t next_sector = 0;
375
    uint32_t next_size = 0;
376
377
    char filename[ROCK_RIDGE_MAX_FILENAME];
378
    while (true) {
379
        // Skip any consecutive slashes
380
        while (*path == '/') {
381
            path++;
382
        }
383
384
        // Check if we've reached the end of the path (handles trailing slashes)
385
        if (*path == '\0') {
386
            // Use the current directory's extent info
387
            // For root, this was set from ret->context->root
388
            // For subdirs, it was set from the last matched entry
389
            if (!first) {
390
                pmm_free(current, current_size);
391
            }
392
            pmm_free(ret, sizeof(struct iso9660_file_handle));
393
            return NULL;
394
        }
395
396
        char *aux = filename;
397
        char *aux_end = filename + ROCK_RIDGE_MAX_FILENAME - 1;
398
        while (!(*path == '/' || *path == '\0')) {
399
            if (aux >= aux_end) {
400
                panic(false, "iso9660: Path component exceeds maximum length");
401
            }
402
            *aux++ = *path++;
403
        }
404
        *aux = '\0';
405
406
        struct iso9660_directory_entry *entry = iso9660_find(current, current_size, filename);
407
        if (!entry) {
408
            if (!first) {
409
                pmm_free(current, current_size);
410
            }
411
            pmm_free(ret, sizeof(struct iso9660_file_handle));
412
            return NULL;    // Not found :(
413
        }
414
415
        next_sector = entry->extent.little;
416
        next_size = entry->extent_size.little;
417
418
        if (*path != '\0' && !(entry->flags & ISO9660_FLAG_DIRECTORY)) {
419
            if (!first) {
420
                pmm_free(current, current_size);
421
            }
422
            pmm_free(ret, sizeof(struct iso9660_file_handle));
423
            return NULL;
424
        }
425
426
        if (*path == '\0') {
427
            // Found the file - collect all extents for multi-extent files
428
            void *buffer_end = (uint8_t *)current + current_size;
429
430
            // First pass: count extents and calculate total size
431
            uint32_t extent_count = 1;
432
            uint64_t total_size = entry->extent_size.little;
433
            struct iso9660_directory_entry *e = entry;
434
435
            // load_name returns false on the ISO-9660 fallback path but
436
            // still populates the buffer; treat an empty buffer as the only
437
            // "no usable name" case.
438
            char base_name[256];
439
            load_name(base_name, sizeof(base_name), entry);
440
441
            while (e->flags & ISO9660_FLAG_MULTI_EXTENT) {
442
                struct iso9660_directory_entry *next = iso9660_next_entry(e, buffer_end);
443
                if (next == NULL)
444
                    break;
445
                // Per ECMA-119, multi-extent continuation records must share
446
                // the file identifier of the first record. Refuse to splice
447
                // in unrelated entries.
448
                char next_name[256];
449
                load_name(next_name, sizeof(next_name), next);
450
                if (base_name[0] == '\0' || strcmp(base_name, next_name) != 0) {
451
                    break;
452
                }
453
                e = next;
454
                extent_count++;
455
                total_size += e->extent_size.little;
456
457
                // Sanity check to prevent runaway on corrupted directories
458
                if (extent_count >= ISO9660_MAX_EXTENT_COUNT) {
459
                    break;
460
                }
461
            }
462
463
            // Allocate extent array
464
            ret->extents = ext_mem_alloc_counted(extent_count, sizeof(struct iso9660_extent));
465
            ret->extent_count = extent_count;
466
            ret->total_size = total_size;
467
468
            // Second pass: populate extent array
469
            e = entry;
470
            for (uint32_t i = 0; i < extent_count; i++) {
471
                ret->extents[i].LBA = e->extent.little;
472
                ret->extents[i].size = e->extent_size.little;
473
                if (i + 1 < extent_count) {
474
                    struct iso9660_directory_entry *next = iso9660_next_entry(e, buffer_end);
475
                    if (next == NULL)
476
                        break;
477
                    e = next;
478
                }
479
            }
480
481
            // Free the directory buffer if we allocated one
482
            if (!first) {
483
                pmm_free(current, current_size);
484
            }
485
486
            goto setup_handle;
487
        }
488
489
        path++;  // Skip the '/' separator
490
491
        if (!first) {
492
            pmm_free(current, current_size);
493
        }
494
495
        // Validate directory size to prevent memory exhaustion, and require
496
        // sector alignment so directory-traversal sector-skip arithmetic is
497
        // sound.
498
        if (next_size == 0 || next_size > ISO9660_MAX_DIR_SIZE
499
         || next_size % ISO9660_SECTOR_SIZE != 0) {
500
            pmm_free(ret, sizeof(struct iso9660_file_handle));
501
            return NULL;
502
        }
503
504
        current_size = next_size;
505
        current = ext_mem_alloc(current_size);
506
507
        first = false;
508
509
        uint64_t dir_offset = (uint64_t)next_sector * ISO9660_SECTOR_SIZE;
510
        if (!volume_read(vol, current, dir_offset, current_size)) {
511
            pmm_free(current, current_size);
512
            pmm_free(ret, sizeof(struct iso9660_file_handle));
513
            return NULL;
514
        }
515
    }
516
517
setup_handle:;
518
    struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle));
519
520
    handle->fd = ret;
521
    handle->read = (void *)iso9660_read;
522
    handle->close = (void *)iso9660_close;
523
    handle->size = ret->total_size;
524
    handle->vol = vol;
525
#if defined (UEFI)
526
    handle->efi_part_handle = vol->efi_part_handle;
527
#endif
528
529
    return handle;
530
}
531
532
static uint64_t iso9660_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) {
533
    uint64_t requested = count;
534
    struct iso9660_file_handle *f = file->fd;
535
536
    // Find which extent 'loc' falls into and read across extents as needed
537
    uint64_t extent_start = 0;
538
    for (uint32_t i = 0; i < f->extent_count && count > 0; i++) {
539
        uint64_t extent_size = f->extents[i].size;
540
        uint64_t extent_end = extent_start + extent_size;
541
542
        if (loc < extent_end) {
543
            // Read starts (or continues) in this extent
544
            uint64_t offset_in_extent = (loc > extent_start) ? (loc - extent_start) : 0;
545
            uint64_t bytes_available = extent_size - offset_in_extent;
546
            uint64_t to_read = (count < bytes_available) ? count : bytes_available;
547
548
            uint64_t disk_offset = (uint64_t)f->extents[i].LBA * ISO9660_SECTOR_SIZE + offset_in_extent;
549
550
            if (!volume_read(f->context->vol, buf, disk_offset, to_read)) {
551
                panic(false, "iso9660: failed to read file data");
552
            }
553
554
            buf = (uint8_t *)buf + to_read;
555
            loc += to_read;
556
            count -= to_read;
557
        }
558
559
        extent_start = extent_end;
560
    }
561
562
    if (count > 0) {
563
        panic(false, "iso9660: read beyond end of file");
564
    }
565
    return requested;
566
}
567
568
static void iso9660_close(struct file_handle *file) {
569
    struct iso9660_file_handle *f = file->fd;
570
    pmm_free(f->extents, f->extent_count * sizeof(struct iso9660_extent));
571
    pmm_free(f, sizeof(struct iso9660_file_handle));
572
}
tab: 248 wrap: offon