From 70b80695cb77032f6ddbf17052a900ff40909ddc Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Mon, 15 Jun 2026 10:53:18 -0700 Subject: [PATCH] Guard log_merge, srch_root, and mounted_client item printers Scoutfs print segfaults walking a log_merge btree that has more than one level. print_btree_block() prints parent (level > 0) items via print_block_ref(), which invokes the item callback with a NULL value to print the key portion before printing the child ref: func(key, 0, 0, NULL, 0, arg); print_log_merge_item immediately casts val and reads a field, dereferencing NULL. A log_merge of a single leaf block (height 1) never hits the parent path; one with height > 1 crashes on the first parent item: scoutfs[22043]: segfault at 8 ip 0000000000408ef0 sp 00007fffc5edd8b0 error 4 #0 0x0000000000408ef0 in print_log_merge_item () #1 0x000000000040958d in print_btree_block.constprop.0.isra () #2 0x000000000040a471 in print_cmd () #3 0x0000000000404264 in cmd_execute () #4 0x00000000004025c9 in main () print_mounted_client_entry has the same bug: it casts val and reads mcv->addr / mcv->flags with no NULL guard, so a mounted_clients btree of height > 1 segfaults the same way. print_srch_root_item guards NULL but casts to scoutfs_srch_compact or scoutfs_srch_file without bounds checking, so a short or malformed item reads past its end. Fix all three: return early when val is NULL (printing just the key for the parent ref where applicable), and bounds-check val_len before each cast so a short item is reported instead of read past its end. Signed-off-by: Auke Kok --- utils/src/print.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/utils/src/print.c b/utils/src/print.c index c17eb425..f3d7ae6a 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -375,6 +375,11 @@ static int print_srch_root_item(struct scoutfs_key *key, u64 seq, u8 flags, void if (val) { if (key->sk_type == SCOUTFS_SRCH_PENDING_TYPE || key->sk_type == SCOUTFS_SRCH_BUSY_TYPE) { + if (val_len < sizeof(*sc)) { + printf(" (short srch compact value: val_len %u)\n", + val_len); + return 0; + } sc = val; printf(" compact %s: nr %u flags 0x%x\n", key->sk_type == SCOUTFS_SRCH_PENDING_TYPE ? @@ -387,6 +392,11 @@ static int print_srch_root_item(struct scoutfs_key *key, u64 seq, u8 flags, void SRF_A(&sc->in[i].sfl)); } } else { + if (val_len < sizeof(*sfl)) { + printf(" (short srch file value: val_len %u)\n", + val_len); + return 0; + } sfl = val; printf(" "SRF_FMT"\n", SRF_A(sfl)); } @@ -398,9 +408,25 @@ static int print_srch_root_item(struct scoutfs_key *key, u64 seq, u8 flags, void static int print_mounted_client_entry(struct scoutfs_key *key, u64 seq, u8 flags, void *val, unsigned val_len, void *arg) { - struct scoutfs_mounted_client_btree_val *mcv = val; + struct scoutfs_mounted_client_btree_val *mcv; struct in_addr in; + /* + * Parent block items reference child blocks and have no value; + * print_block_ref() calls us with a NULL val just to print the key. + */ + if (!val) { + printf(" rid %016llx\n", le64_to_cpu(key->skmc_rid)); + return 0; + } + + if (val_len < sizeof(*mcv)) { + printf(" rid %016llx (short mounted client value: val_len %u)\n", + le64_to_cpu(key->skmc_rid), val_len); + return 0; + } + + mcv = val; memset(&in, 0, sizeof(in)); in.s_addr = htonl(le32_to_cpu(mcv->addr.v4.addr)); @@ -419,8 +445,17 @@ static int print_log_merge_item(struct scoutfs_key *key, u64 seq, u8 flags, void struct scoutfs_log_merge_complete *comp; struct scoutfs_log_merge_freeing *fr; + /* + * Parent block items reference child blocks and have no value; + * print_block_ref() calls us with a NULL val just to print the key. + */ + if (!val) + return 0; + switch (key->sk_zone) { case SCOUTFS_LOG_MERGE_STATUS_ZONE: + if (val_len < sizeof(*stat)) + goto bad_len; stat = val; printf(" status: next_range_key "SK_FMT" nr_req %llu nr_comp %llu seq %llu\n", SK_ARG(&stat->next_range_key), @@ -429,12 +464,16 @@ static int print_log_merge_item(struct scoutfs_key *key, u64 seq, u8 flags, void le64_to_cpu(stat->seq)); break; case SCOUTFS_LOG_MERGE_RANGE_ZONE: + if (val_len < sizeof(*rng)) + goto bad_len; rng = val; printf(" range: start "SK_FMT" end "SK_FMT"\n", SK_ARG(&rng->start), SK_ARG(&rng->end)); break; case SCOUTFS_LOG_MERGE_REQUEST_ZONE: + if (val_len < sizeof(*req)) + goto bad_len; req = val; printf(" request: logs_root "BTROOT_F" logs_root "BTROOT_F" start "SK_FMT " end "SK_FMT" input_seq %llu rid %016llx seq %llu flags 0x%llx\n", @@ -448,6 +487,8 @@ static int print_log_merge_item(struct scoutfs_key *key, u64 seq, u8 flags, void le64_to_cpu(req->flags)); break; case SCOUTFS_LOG_MERGE_COMPLETE_ZONE: + if (val_len < sizeof(*comp)) + goto bad_len; comp = val; printf(" complete: root "BTROOT_F" start "SK_FMT" end "SK_FMT " remain "SK_FMT" rid %016llx seq %llu flags %llx\n", @@ -460,6 +501,8 @@ static int print_log_merge_item(struct scoutfs_key *key, u64 seq, u8 flags, void le64_to_cpu(comp->flags)); break; case SCOUTFS_LOG_MERGE_FREEING_ZONE: + if (val_len < sizeof(*fr)) + goto bad_len; fr = val; printf(" freeing: root "BTROOT_F" key "SK_FMT" seq %llu\n", BTROOT_A(&fr->root), @@ -472,6 +515,11 @@ static int print_log_merge_item(struct scoutfs_key *key, u64 seq, u8 flags, void } return 0; + +bad_len: + printf(" (short log merge value: zone %u val_len %u)\n", + key->sk_zone, val_len); + return 0; } static int print_alloc_item(struct scoutfs_key *key, u64 seq, u8 flags, void *val,