diff --git a/tools/iso-patch/engine/ext4.c b/tools/iso-patch/engine/ext4.c index 1cfce9e..41eed2e 100644 --- a/tools/iso-patch/engine/ext4.c +++ b/tools/iso-patch/engine/ext4.c @@ -30,7 +30,7 @@ #define EXT4_FIRST_INO 11u /* s_first_ino: first non-reserved */ /* Feature flags - kept very small. */ -#define EXT4_FEATURE_COMPAT 0 +#define EXT4_FEATURE_COMPAT 0x08u #define EXT4_FEATURE_INCOMPAT (0x02u | 0x40u) /* FILETYPE | EXTENTS */ #define EXT4_FEATURE_RO_COMPAT (0x01u | 0x02u | 0x08u | 0x20u | 0x40u) /* SPARSE_SUPER | LARGE_FILE | HUGE_FILE @@ -224,6 +224,15 @@ typedef struct { } ext4_inode_t; /* sizeof = 0xa0 = 160. Inode-size 256 leaves 96 trailing bytes (xattrs). */ +typedef struct { + uint8_t e_name_len; + uint8_t e_name_index; + uint16_t e_value_offs; + uint32_t e_value_inum; + uint32_t e_value_size; + uint32_t e_hash; +} ext4_xattr_entry_t; + typedef struct { uint16_t eh_magic; uint16_t eh_entries; @@ -291,6 +300,8 @@ typedef struct { /* For dirs only - serialized entry blob (filled at close). */ uint8_t *dir_blob; uint32_t dir_blob_len; + void *capability; + uint32_t capability_size; } inode_rec_t; struct ext4_writer { @@ -1022,6 +1033,29 @@ int ext4_writer_add_file(ext4_writer_t *w, const char *path, return 0; } +int ext4_writer_set_capability(ext4_writer_t *w, const char *path, + const void *data, size_t size) +{ + if (!data || (size != 12 && size != 20 && size != 24)) return -1; + dir_node_t *parent; + char leaf[256]; + if (split_path(w, path, &parent, leaf, sizeof(leaf)) != 0) return -1; + for (dir_entry_rec_t *e = parent->entries; e; e = e->next) { + if (strcmp(e->name, leaf) != 0) continue; + if (e->file_type != EXT4_FT_REG_FILE) return -1; + inode_rec_t *r = w->inodes[e->inode]; + void *copy = malloc(size); + if (!copy) return -1; + memcpy(copy, data, size); + free(r->capability); + r->capability = copy; + r->capability_size = (uint32_t)size; + r->body.i_extra_isize = (uint16_t)(sizeof(ext4_inode_t) - 128); + return 0; + } + return -1; +} + int ext4_writer_add_symlink(ext4_writer_t *w, const char *path, const char *target, size_t target_len, uint32_t uid, uint32_t gid, uint32_t mtime) @@ -1232,6 +1266,22 @@ static int write_all_inodes(ext4_writer_t *w) memcpy(tbl + idx * INODE_SIZE, &w->inodes[inode_num]->body, sizeof(ext4_inode_t)); + inode_rec_t *r = w->inodes[inode_num]; + if (r->capability_size) { + uint8_t *inode = tbl + idx * INODE_SIZE; + uint32_t magic = 0xEA020000u; + memcpy(inode + sizeof(ext4_inode_t), &magic, sizeof(magic)); + ext4_xattr_entry_t *entry = (ext4_xattr_entry_t *)( + inode + sizeof(ext4_inode_t) + sizeof(magic)); + entry->e_name_len = 10; + entry->e_name_index = 6; + entry->e_value_offs = (uint16_t)(INODE_SIZE - r->capability_size - + sizeof(ext4_inode_t) - sizeof(magic)); + entry->e_value_size = r->capability_size; + memcpy(entry + 1, "capability", entry->e_name_len); + memcpy(inode + INODE_SIZE - r->capability_size, + r->capability, r->capability_size); + } any = 1; } if (!any) continue; @@ -1474,6 +1524,7 @@ int ext4_writer_close(ext4_writer_t *w) for (uint32_t i = 1; i <= w->total_inodes; i++) { if (w->inodes[i]) { free(w->inodes[i]->dir_blob); + free(w->inodes[i]->capability); free(w->inodes[i]); } } diff --git a/tools/iso-patch/engine/ext4.h b/tools/iso-patch/engine/ext4.h index a46ee29..a647668 100644 --- a/tools/iso-patch/engine/ext4.h +++ b/tools/iso-patch/engine/ext4.h @@ -3,7 +3,6 @@ * Feature subset (deliberately small): * INCOMPAT : FILETYPE | EXTENTS * RO_COMPAT : SPARSE_SUPER | LARGE_FILE | HUGE_FILE | DIR_NLINK | EXTRA_ISIZE - * No journal, no metadata_csum, no xattrs, no flex_bg, no 64bit, no HTREE. * * Layout: * block_size = 4096 @@ -47,6 +46,8 @@ int ext4_writer_add_dir(ext4_writer_t *w, const char *path, int ext4_writer_add_file(ext4_writer_t *w, const char *path, uint16_t perms, uint32_t uid, uint32_t gid, uint32_t mtime, const void *data, uint64_t size); +int ext4_writer_set_capability(ext4_writer_t *w, const char *path, + const void *data, size_t size); int ext4_writer_add_symlink(ext4_writer_t *w, const char *path, const char *target, size_t target_len, uint32_t uid, uint32_t gid, uint32_t mtime); diff --git a/tools/iso-patch/engine/squashfs.c b/tools/iso-patch/engine/squashfs.c index 743455c..8cda572 100644 --- a/tools/iso-patch/engine/squashfs.c +++ b/tools/iso-patch/engine/squashfs.c @@ -453,6 +453,143 @@ int sqfs_read_metadata_block(sqfs_ctx_t *ctx, uint64_t *off, return -1; } +typedef struct { + uint64_t next_block; + uint64_t end; + size_t pos; + size_t len; + uint8_t data[SQFS_METADATA_SIZE]; +} sqfs_xattr_cursor_t; + +static int xattr_next_block(sqfs_ctx_t *ctx, sqfs_xattr_cursor_t *c) +{ + uint16_t hdr; + if (c->next_block >= c->end || c->end - c->next_block < 2 || + sqfs_read_raw(ctx, c->next_block, &hdr, sizeof(hdr)) != 0 || + (hdr & 0x7fff) > c->end - c->next_block - 2) + return -1; + if (sqfs_read_metadata_block(ctx, &c->next_block, c->data, &c->len) != 0 || + c->len == 0 || (c->next_block != c->end && c->len != SQFS_METADATA_SIZE)) + return -1; + c->pos = 0; + return 0; +} + +static int xattr_cursor_start(sqfs_ctx_t *ctx, sqfs_xattr_cursor_t *c, + uint64_t start, uint64_t end, size_t offset) +{ + if (end > ctx->sb.bytes_used || start >= end || offset >= SQFS_METADATA_SIZE) + return -1; + c->next_block = start; + c->end = end; + if (xattr_next_block(ctx, c) != 0 || offset >= c->len) return -1; + c->pos = offset; + return 0; +} + +static int xattr_read(sqfs_ctx_t *ctx, sqfs_xattr_cursor_t *c, + void *out, size_t size) +{ + uint8_t *p = (uint8_t *)out; + while (size) { + if (c->pos == c->len && xattr_next_block(ctx, c) != 0) return -1; + size_t n = c->len - c->pos; + if (n > size) n = size; + if (p) { memcpy(p, c->data + c->pos, n); p += n; } + c->pos += n; + size -= n; + } + return 0; +} + +int sqfs_read_capability(sqfs_ctx_t *ctx, const sqfs_entry_t *entry, + void **out_data, size_t *out_size) +{ + *out_data = NULL; + *out_size = 0; + if (entry->type != SQFS_EREG_TYPE || entry->_xattr_idx == UINT32_MAX) + return 0; + + struct { + uint64_t start; + uint32_t count; + uint32_t unused; + } table; + struct { + uint64_t ref; + uint32_t count; + uint32_t size; + } id; + uint64_t table_off = ctx->sb.xattr_id_table_start; + if (table_off > ctx->sb.bytes_used || + sizeof(table) > ctx->sb.bytes_used - table_off || + sqfs_read_raw(ctx, table_off, &table, sizeof(table)) != 0 || + entry->_xattr_idx >= table.count) + return -1; + + uint64_t index_bytes = (((uint64_t)table.count * sizeof(id) + + SQFS_METADATA_SIZE - 1) / SQFS_METADATA_SIZE) * 8; + table_off += sizeof(table); + if (index_bytes != ctx->sb.bytes_used - table_off) return -1; + uint64_t xattr_end, id_block; + uint64_t id_offset = (uint64_t)entry->_xattr_idx * sizeof(id); + if (sqfs_read_raw(ctx, table_off, &xattr_end, sizeof(xattr_end)) != 0 || + sqfs_read_raw(ctx, table_off + (id_offset / SQFS_METADATA_SIZE) * 8, + &id_block, sizeof(id_block)) != 0 || + table.start < sizeof(sqfs_superblock_t) || table.start >= xattr_end || + xattr_end > id_block || id_block >= ctx->sb.xattr_id_table_start) + return -1; + + sqfs_xattr_cursor_t c; + if (xattr_cursor_start(ctx, &c, id_block, ctx->sb.xattr_id_table_start, + (size_t)(id_offset % SQFS_METADATA_SIZE)) != 0 || + xattr_read(ctx, &c, &id, sizeof(id)) != 0 || + id.count == 0 || + (id.ref >> 16) >= xattr_end - table.start || + xattr_cursor_start(ctx, &c, table.start + (id.ref >> 16), xattr_end, + (size_t)(id.ref & 0xffff)) != 0) + return -1; + + for (uint32_t i = 0; i < id.count; i++) { + uint16_t attr[2]; + char name[sizeof("capability") - 1]; + uint32_t value_size; + if (xattr_read(ctx, &c, attr, sizeof(attr)) != 0 || + attr[1] == 0 || (attr[0] & ~0x1ffu) != 0) + return -1; + int match = (attr[0] & 0xff) == 2 && attr[1] == sizeof(name); + if (xattr_read(ctx, &c, match ? name : NULL, attr[1]) != 0 || + xattr_read(ctx, &c, &value_size, sizeof(value_size)) != 0 || + ((attr[0] & 0x100) && value_size != sizeof(uint64_t))) + return -1; + if (!match || memcmp(name, "capability", sizeof(name)) != 0) { + if (xattr_read(ctx, &c, NULL, value_size) != 0) return -1; + continue; + } + + if (attr[0] & 0x100) { + uint64_t ref; + if (xattr_read(ctx, &c, &ref, sizeof(ref)) != 0 || + (ref >> 16) >= xattr_end - table.start || + xattr_cursor_start(ctx, &c, table.start + (ref >> 16), xattr_end, + (size_t)(ref & 0xffff)) != 0 || + xattr_read(ctx, &c, &value_size, sizeof(value_size)) != 0) + return -1; + } + if (value_size != 12 && value_size != 20 && value_size != 24) return -1; + void *value = malloc(value_size); + if (!value) return -1; + if (xattr_read(ctx, &c, value, value_size) != 0) { + free(value); + return -1; + } + *out_data = value; + *out_size = value_size; + return 0; + } + return 0; +} + /* ---- Inode + directory parsing ---- * * All structs are stored little-endian. We use memcpy into local typed @@ -688,6 +825,7 @@ static int walk_inode(walk_state_t *w, uint64_t inode_ref) e.gid = id_lookup(ctx, hdr.gid_idx); e.mtime = hdr.mtime; e._file_inode_off = (size_t)(p - ctx->inode_stream.buf); + e._xattr_idx = UINT32_MAX; switch (hdr.type) { case SQFS_DIR_TYPE: { @@ -730,6 +868,7 @@ static int walk_inode(walk_state_t *w, uint64_t inode_ref) sqfs_ext_file_t f; memcpy(&f, body, sizeof(f)); e.size = f.file_size; + e._xattr_idx = f.xattr_idx; if (w->cb_rc == 0) w->cb_rc = w->cb(&e, w->user); w->n_files++; return w->cb_rc; diff --git a/tools/iso-patch/engine/squashfs.h b/tools/iso-patch/engine/squashfs.h index 7ac7fd7..f94575f 100644 --- a/tools/iso-patch/engine/squashfs.h +++ b/tools/iso-patch/engine/squashfs.h @@ -118,6 +118,7 @@ typedef struct { /* For regular files, set by the walker, used by the callback to fetch * the bytes (deferred so the callback can stream / skip as needed). */ uint64_t _file_inode_off; /* internal: offset of file's inode in table */ + uint32_t _xattr_idx; } sqfs_entry_t; /* Callback signature. Return 0 to continue, non-zero to abort traversal. */ @@ -135,4 +136,7 @@ int sqfs_walk(sqfs_ctx_t *ctx, sqfs_walk_cb_t cb, void *user); int sqfs_read_file(sqfs_ctx_t *ctx, const sqfs_entry_t *entry, void **out_data, size_t *out_size); +int sqfs_read_capability(sqfs_ctx_t *ctx, const sqfs_entry_t *entry, + void **out_data, size_t *out_size); + #endif diff --git a/tools/iso-patch/ubuntu_vhdx.c b/tools/iso-patch/ubuntu_vhdx.c index c816a78..f036ed8 100644 --- a/tools/iso-patch/ubuntu_vhdx.c +++ b/tools/iso-patch/ubuntu_vhdx.c @@ -544,6 +544,8 @@ typedef struct slot { uint32_t uid, gid, mtime, rdev; uint64_t size; void *data; + void *capability; + size_t capability_size; char *symlink_target; uint32_t symlink_target_size; uint64_t mem_bytes; @@ -707,6 +709,14 @@ static DWORD WINAPI decompress_worker(LPVOID arg) } s->size = sz; s->mem_bytes += sz; + if (sqfs_read_capability(p->sq, &e, &s->capability, &s->capability_size) != 0) { + log_err(L"sqfs_read_capability failed"); + free(s->data); free(s->path); free(s); + InterlockedIncrement((volatile LONG *)&p->n_errors); + free(w); + continue; + } + s->mem_bytes += s->capability_size; pipeline_push(p, s); free(w); } @@ -783,6 +793,9 @@ static DWORD WINAPI consumer_thread(LPVOID arg) case SQFS_REG_TYPE: case SQFS_EREG_TYPE: rc = ext4_writer_add_file(p->ew, s->path, s->mode, s->uid, s->gid, s->mtime, s->data, s->size); + if (rc == 0 && s->capability_size) + rc = ext4_writer_set_capability(p->ew, s->path, + s->capability, s->capability_size); if (rc == 0) { p->n_files++; p->bytes_total += s->size; } /* Mirror unicode.pf2 into /boot/grub/fonts/ (loadfont's default * search path). */ @@ -832,6 +845,7 @@ static DWORD WINAPI consumer_thread(LPVOID arg) free(s->path); free(s->data); + free(s->capability); free(s->symlink_target); free(s); } @@ -1281,6 +1295,7 @@ static void plant_firstboot_service(ext4_writer_t *ew) "# in one shot — saves repeated apt overhead and avoids the\n" "# install-build-deps-after-trying-to-build ordering bug.\n" "echo \"==== STEP 7.4: apt sources + install all build tools ====\"\n" + "rm -f /etc/apt/sources.list.d/cdrom.sources\n" "APT_SOURCES_DIR=/etc/apt/appsandbox-sources.list.d\n" "install -d \"$APT_SOURCES_DIR\"\n" "if [ -d /opt/appsandbox/local-apt/dists ]; then\n"