DF-2640 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | --- a/sys/vfs/hammer2/hammer2_chain.c 2026-06-29 12:51:19.000000000 +0000 +++ b/sys/vfs/hammer2/hammer2_chain.c 2026-08-29 13:45:07.280745947 +0000 @@ -5772,6 +5772,14 @@ const hammer2_inode_data_t *ripdata; if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) { + /* + * DF-2640: an INODE chain whose data could not be loaded + * (I/O or check error), or whose media data_off is 0 + * (unresolvable, see hammer2_chain_load_data()), must not + * be dereferenced here. + */ + if (chain->error || chain->data == NULL) + return 0; ripdata = &chain->data->ipdata; if (ripdata->meta.name_len == name_len && bcmp(ripdata->filename, name, name_len) == 0) { @@ -5780,9 +5788,18 @@ } if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT && chain->bref.embed.dirent.namlen == name_len) { - if (name_len > sizeof(chain->bref.check.buf) && - bcmp(chain->data->buf, name, name_len) == 0) { - return 1; + /* + * DF-2640: long names live in the dirent's data block. + * If the data could not be loaded or data_off is 0 the + * name is unreadable; do not dereference chain->data. + * (Short names are embedded in the bref itself and are + * still matchable below.) + */ + if (name_len > sizeof(chain->bref.check.buf)) { + if (chain->error || chain->data == NULL) + return 0; + if (bcmp(chain->data->buf, name, name_len) == 0) + return 1; } if (name_len <= sizeof(chain->bref.check.buf) && bcmp(chain->bref.check.buf, name, name_len) == 0) { --- a/sys/vfs/hammer2/hammer2_xops.c 2026-07-13 22:27:02.745458170 +0000 +++ b/sys/vfs/hammer2/hammer2_xops.c 2026-08-29 13:45:07.308745592 +0000 @@ -399,8 +399,12 @@ if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT) { type = chain->bref.embed.dirent.type; dopermanent |= HAMMER2_DELETE_PERMANENT; - } else { + } else if (chain->data) { type = chain->data->ipdata.meta.type; + } else { + error = chain->error ? chain->error : + HAMMER2_ERROR_EIO; + goto done; } /* @@ -536,6 +540,15 @@ parent = NULL; goto done; } + if (chain->error || chain->data == NULL) { + error = chain->error ? chain->error : + HAMMER2_ERROR_EIO; + hammer2_chain_unlock(chain); + hammer2_chain_drop(chain); + chain = NULL; + parent = NULL; + goto done; + } type = chain->data->ipdata.meta.type; if (type == HAMMER2_OBJTYPE_DIRECTORY && (error = checkdirempty(NULL, chain, clindex)) != 0) @@ -666,6 +679,8 @@ error = hammer2_chain_modify(chain, xop->head.mtid, 0, 0); + if (error == 0 && chain->data == NULL) + error = HAMMER2_ERROR_EIO; if (error == 0) { wipdata = &chain->data->ipdata; @@ -714,6 +729,8 @@ chain, xop->head.mtid, 0, 0); } + if (error == 0 && chain->data == NULL) + error = HAMMER2_ERROR_EIO; if (error == 0) { bzero(chain->data->buf, chain->bytes); bcopy(xop->head.name2, @@ -733,7 +750,9 @@ * authority, but adjust the inode's iparent field too if the inode * is embedded in the directory. */ - if (chain->bref.type == HAMMER2_BREF_TYPE_INODE && + if (error == 0 && + chain->bref.type == HAMMER2_BREF_TYPE_INODE && + chain->data != NULL && chain->data->ipdata.meta.iparent != xop->head.ip3->meta.inum) { hammer2_inode_data_t *wipdata; @@ -1614,6 +1633,8 @@ */ if (error == 0) { error = hammer2_chain_modify(parent, xop->head.mtid, 0, 0); + if (error == 0 && parent->data == NULL) + error = HAMMER2_ERROR_EIO; if (error == 0) { parent->data->ipdata.meta = xop->meta; if (xop->clear_directdata) { |