[PATCH v4] erofs-utils: fsck: add `--xattr-inode-digest` support
Gao Xiang <[email protected]> Sat, 27 Jun 2026 11:03:39 +0800
| Newsgroups | org.ozlabs.lists.linux-erofs |
|---|---|
| Message-ID | <[email protected]> |
From: Chengyu Zhu <[email protected]> Add a new `--xattr-inode-digest` option to verify per-inode digests embedded by `mkfs.erofs --xattr-inode-digest`. For each regular inode with non-zero size, the stored digest xattr is compared against a freshly computed SHA-256 of the inode data: Any mismatch is reported as filesystem corruption. Note that enabling this option also forces extraction. Signed-off-by: Chengyu Zhu <[email protected]> Signed-off-by: Gao Xiang <[email protected]> --- v4: - fix a false-positive compiler warning. fsck/main.c | 129 +++++++++++++++++++++++++++++++++++++----- include/erofs/xattr.h | 3 + lib/xattr.c | 56 ++++++++++++++++-- man/fsck.erofs.1 | 4 ++ 4 files changed, 174 insertions(+), 18 deletions(-) diff --git a/fsck/main.c b/fsck/main.c index 759ca7df6722..b63fd135d7ad 100644 --- a/fsck/main.c +++ b/fsck/main.c @@ -15,9 +15,12 @@ #include "erofs/xattr.h" #include "../lib/compressor.h" #include "../lib/liberofs_compress.h" +#include "../lib/sha256.h" static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid); +static char erofsfsck_nullstr[] = ""; + struct erofsfsck_dirstack { erofs_nid_t dirs[PATH_MAX]; int top; @@ -29,6 +32,7 @@ struct erofsfsck_cfg { u64 logical_blocks; char *extract_path; size_t extract_pos; + char *digest_xattr_name; mode_t umask; bool superuser; bool corrupted; @@ -64,6 +68,7 @@ static struct option long_options[] = { {"nid", required_argument, 0, 15}, {"path", required_argument, 0, 16}, {"no-sbcrc", no_argument, 0, 512}, + {"xattr-inode-digest", no_argument, 0, 17}, {0, 0, 0, 0}, }; @@ -117,6 +122,7 @@ static void usage(int argc, char **argv) " --nid=# check or extract from the target inode of nid #\n" " --path=X check or extract from the target inode of path X\n" " --no-sbcrc bypass the superblock checksum verification\n" + " --xattr-inode-digest verify per-inode digests recorded as extended attributes\n" " --[no-]xattrs whether to dump extended attributes (default off)\n" "\n" " -a, -A, -y no-op, for compatibility with fsck of other filesystems\n" @@ -257,6 +263,10 @@ static int erofsfsck_parse_options_cfg(int argc, char **argv) case 16: fsckcfg.inode_path = optarg; break; + case 17: + fsckcfg.digest_xattr_name = erofsfsck_nullstr; + fsckcfg.check_decomp = true; + break; case 512: fsckcfg.nosbcrc = true; break; @@ -503,7 +513,8 @@ out: return ret; } -static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd) +static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd, + struct sha256_state *digest) { struct erofs_map_blocks map = { .buf = __EROFS_BUF_INITIALIZER, @@ -546,11 +557,24 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd) if (map.m_la >= inode->i_size || !needdecode) continue; - if (outfd >= 0 && !(map.m_flags & EROFS_MAP_MAPPED)) { - ret = lseek(outfd, map.m_llen, SEEK_CUR); - if (ret < 0) { - ret = -errno; - goto out; + if (!(map.m_flags & EROFS_MAP_MAPPED)) { + if (digest) { + static const char zeros[4096]; + u64 remain = map.m_llen; + + while (remain > 0) { + u64 chunk = remain > sizeof(zeros) ? + sizeof(zeros) : remain; + erofs_sha256_process(digest, + (const u8 *)zeros, chunk); + remain -= chunk; + } + } else if (outfd >= 0) { + ret = lseek(outfd, map.m_llen, SEEK_CUR); + if (ret < 0) { + ret = -errno; + goto out; + } } continue; } @@ -596,6 +620,9 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd) if (ret) goto out; + if (digest) + erofs_sha256_process(digest, + (const u8 *)buffer, map.m_llen); if (outfd >= 0 && write(outfd, buffer, map.m_llen) < 0) goto fail_eio; } else { @@ -609,6 +636,9 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd) if (ret) goto out; + if (digest) + erofs_sha256_process(digest, + (const u8 *)raw, count); if (outfd >= 0 && write(outfd, raw, count) < 0) goto fail_eio; map.m_llen -= count; @@ -643,7 +673,7 @@ static inline int erofs_extract_dir(struct erofs_inode *inode) erofs_dbg("create directory %s", fsckcfg.extract_path); /* verify data chunk layout */ - ret = erofs_verify_inode_data(inode, -1); + ret = erofs_verify_inode_data(inode, -1, NULL); if (ret) return ret; @@ -739,6 +769,56 @@ static void erofsfsck_hardlink_exit(void) } } +static int erofsfsck_verify_file_digest(struct erofs_inode *inode, + const u8 *digest) +{ + u8 stored[32 + sizeof("sha256:") - 1]; + int ret; + + ret = __erofs_getxattr(inode, fsckcfg.digest_xattr_name, + (char *)stored, sizeof(stored), true); + if (ret == -ENODATA) { + erofs_warn("no digest xattr for nid %llu, skipped", + inode->nid | 0ULL); + return 0; + } else if (ret < 0) + return ret; + + if (ret != sizeof(stored) || + memcmp(stored, "sha256:", sizeof("sha256:") - 1)) { + erofs_err("unidentified digest xattr @ nid %llu (size=%d)", + inode->nid | 0ULL, ret); + return -EFSCORRUPTED; + } + + if (memcmp(digest, stored + sizeof("sha256:") - 1, 32)) { + erofs_err("digest MISMATCH @ nid %llu", + inode->nid | 0ULL); + return -EFSCORRUPTED; + } + return 0; +} + +static int erofsfsck_calc_inode_data(struct erofs_inode *inode, int outfd) +{ + int ret; + + if (fsckcfg.digest_xattr_name && + S_ISREG(inode->i_mode) && inode->i_size > 0) { + struct sha256_state md; + u8 out[32]; + + erofs_sha256_init(&md); + ret = erofs_verify_inode_data(inode, outfd, &md); + erofs_sha256_done(&md, out); + + if (ret) + return ret; + return erofsfsck_verify_file_digest(inode, out); + } + return erofs_verify_inode_data(inode, outfd, NULL); +} + static inline int erofs_extract_file(struct erofs_inode *inode) { bool tryagain = true; @@ -774,8 +854,7 @@ again: return -errno; } - /* verify data chunk layout */ - ret = erofs_verify_inode_data(inode, fd); + ret = erofsfsck_calc_inode_data(inode, fd); close(fd); return ret; } @@ -791,7 +870,7 @@ static inline int erofs_extract_symlink(struct erofs_inode *inode) erofs_dbg("extract symlink to path: %s", fsckcfg.extract_path); /* verify data chunk layout */ - ret = erofs_verify_inode_data(inode, -1); + ret = erofs_verify_inode_data(inode, -1, NULL); if (ret) return ret; @@ -845,7 +924,7 @@ static int erofs_extract_special(struct erofs_inode *inode) erofs_dbg("extract special to path: %s", fsckcfg.extract_path); /* verify data chunk layout */ - ret = erofs_verify_inode_data(inode, -1); + ret = erofs_verify_inode_data(inode, -1, NULL); if (ret) return ret; @@ -928,13 +1007,13 @@ static int erofsfsck_dirent_iter(struct erofs_dir_context *ctx) static int erofsfsck_extract_inode(struct erofs_inode *inode) { - int ret; char *oldpath; + int ret; if (!fsckcfg.extract_path || erofs_is_packed_inode(inode)) { verify: /* verify data chunk layout */ - return erofs_verify_inode_data(inode, -1); + return erofsfsck_calc_inode_data(inode, -1); } oldpath = erofsfsck_hardlink_find(inode->nid); @@ -968,7 +1047,8 @@ verify: inode->i_mode, inode->nid | 0ULL); goto verify; } - if (ret && ret != -ECANCELED) + + if (ret && (ret != -ECANCELED || fsckcfg.digest_xattr_name)) return ret; /* record nid and old path for hardlink */ @@ -1097,6 +1177,25 @@ int main(int argc, char *argv[]) goto exit_put_super; } + if (fsckcfg.digest_xattr_name == erofsfsck_nullstr) { + fsckcfg.digest_xattr_name = + erofs_xattr_get_ishare_prefix(&g_sbi); + if (IS_ERR(fsckcfg.digest_xattr_name)) { + err = PTR_ERR(fsckcfg.digest_xattr_name); + erofs_err("failed to get ishare prefix: %s", + erofs_strerror(err)); + goto exit_put_super; + } + + if (!fsckcfg.digest_xattr_name) { + erofs_err("image has no inode digest xattrs (was --xattr-inode-digest used during mkfs?)"); + err = -ENODATA; + goto exit_put_super; + } + erofs_info("verifying digests using xattr \"%s\"", + fsckcfg.digest_xattr_name); + } + if (fsckcfg.extract_path) erofsfsck_hardlink_init(); @@ -1177,6 +1276,8 @@ exit_hardlink: if (fsckcfg.extract_path) erofsfsck_hardlink_exit(); exit_put_super: + if (fsckcfg.digest_xattr_name != erofsfsck_nullstr) + free(fsckcfg.digest_xattr_name); erofs_put_super(&g_sbi); exit_dev_close: erofs_dev_close(&g_sbi); diff --git a/include/erofs/xattr.h b/include/erofs/xattr.h index 235688649592..5fe3e91a4054 100644 --- a/include/erofs/xattr.h +++ b/include/erofs/xattr.h @@ -35,9 +35,12 @@ int erofs_load_shared_xattrs_from_path(struct erofs_sb_info *sbi, const char *pa int erofs_xattr_insert_name_prefix(const char *prefix); int erofs_xattr_set_ishare_prefix(struct erofs_sb_info *sbi, const char *prefix); +char *erofs_xattr_get_ishare_prefix(struct erofs_sb_info *sbi); void erofs_xattr_cleanup_name_prefixes(void); int erofs_xattr_flush_name_prefixes(struct erofs_importer *im, bool plain); int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi); +int __erofs_getxattr(struct erofs_inode *vi, const char *name, + char *buffer, size_t buffer_size, bool hidden); int erofs_setxattr(struct erofs_inode *inode, int index, const char *name, const void *value, size_t size); int erofs_vfs_setxattr(struct erofs_inode *inode, const char *name, diff --git a/lib/xattr.c b/lib/xattr.c index 1891ac3f23ef..051fdd846146 100644 --- a/lib/xattr.c +++ b/lib/xattr.c @@ -1410,8 +1410,8 @@ static int erofs_xattr_iter_shared(struct erofs_xattr_iter *it, return ret; } -int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, - size_t buffer_size) +int __erofs_getxattr(struct erofs_inode *vi, const char *name, + char *buffer, size_t buffer_size, bool hidden) { int ret; unsigned int prefix, prefixlen; @@ -1424,8 +1424,12 @@ int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, if (ret) return ret; - if (!erofs_xattr_prefix_matches(name, &prefix, &prefixlen)) - return -ENODATA; + if (!erofs_xattr_prefix_matches(name, &prefix, &prefixlen)) { + if (!hidden) + return -ENODATA; + prefixlen = 0; + prefix = 0; + } it.index = prefix; it.name = name + prefixlen; it.len = strlen(it.name); @@ -1445,6 +1449,12 @@ int erofs_getxattr(struct erofs_inode *vi, const char *name, char *buffer, return ret ? ret : it.buffer_ofs; } +int erofs_getxattr(struct erofs_inode *vi, const char *name, + char *buffer, size_t buffer_size) +{ + return __erofs_getxattr(vi, name, buffer, buffer_size, false); +} + int erofs_listxattr(struct erofs_inode *vi, char *buffer, size_t buffer_size) { int ret; @@ -1515,6 +1525,44 @@ int erofs_xattr_set_ishare_prefix(struct erofs_sb_info *sbi, return 0; } +char *erofs_xattr_get_ishare_prefix(struct erofs_sb_info *sbi) +{ + struct erofs_xattr_prefix_item *pf = NULL; + unsigned int idx, base_index; + size_t base_len, infix_len; + char *name; + + if (!erofs_sb_has_ishare_xattrs(sbi)) + return NULL; + + if (sbi->ishare_xattr_prefix_id & EROFS_XATTR_LONG_PREFIX) { + idx = sbi->ishare_xattr_prefix_id & EROFS_XATTR_LONG_PREFIX_MASK; + if (idx >= sbi->xattr_prefix_count) + return NULL; + + pf = &sbi->xattr_prefixes[idx]; + base_index = pf->prefix->base_index; + infix_len = pf->infix_len; + } else { + base_index = sbi->ishare_xattr_prefix_id & + EROFS_XATTR_LONG_PREFIX_MASK; + infix_len = 0; + } + if (base_index >= ARRAY_SIZE(xattr_types)) + return ERR_PTR(-EFSCORRUPTED); + + base_len = xattr_types[base_index].prefix_len; + name = malloc(base_len + infix_len + 1); + if (!name) + return ERR_PTR(-ENOMEM); + + memcpy(name, xattr_types[base_index].prefix, base_len); + if (infix_len) + memcpy(name + base_len, pf->prefix->infix, infix_len); + name[base_len + infix_len] = '\0'; + return name; +} + void erofs_xattr_cleanup_name_prefixes(void) { struct ea_type_node *tnode, *n; diff --git a/man/fsck.erofs.1 b/man/fsck.erofs.1 index 0f698da3b9b7..b2d35a7ded70 100644 --- a/man/fsck.erofs.1 +++ b/man/fsck.erofs.1 @@ -48,6 +48,10 @@ Specify the target inode by its path for checking or extraction. If both .BI "--[no-]xattrs" Whether to dump extended attributes during extraction (default off). .TP +.B "\-\-xattr-inode-digest" +Verify per-inode digests recorded as extended attributes during image +creation with \fBmkfs.erofs \-\-xattr-inode-digest\fR. +.TP \fB\-h\fR, \fB\-\-help\fR Display help string and exit. .TP -- 2.43.5