ext4_split_extent_at() retries after ext4_ext_insert_extent() fails by refinding the original extent and restoring its length. That recovery is only safe for transient resource failures such as -ENOSPC, -EDQUOT, and -ENOMEM. When ext4_ext_insert_extent() fails because the extent tree is already corrupted, ext4_find_extent() can return a leaf path without p_ext. ext4_split_extent_at() then dereferences path[depth].p_ext while trying to fix up the original extent length, causing a NULL pointer dereference while handling a pre-existing filesystem corruption. Do not enter the recovery path for corruption errors, and validate p_ext after refinding the extent before touching it. This keeps the recovery path limited to cases it can actually repair and turns the syzbot-triggered crash into a proper corruption report.
Unified · Split
| @@ -3254,6 +3254,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, · 3253 unmodified lines | ||
| 3254 | 3254 | |
| 3255 | 3255 | insert_err = PTR_ERR(path); |
| 3256 | 3256 | err = 0; |
| 3257 | + if (insert_err != -ENOSPC && insert_err != -EDQUOT && | |
| 3258 | + insert_err != -ENOMEM) | |
| 3259 | + goto out_path; | |
| 3257 | 3260 | |
| 3258 | 3261 | /* |
| 3259 | 3262 | * Get a new path to try to zeroout or fix the extent length. |
| @@ -3270,13 +3273,20 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, · 10 unmodified lines | ||
| 3270 | 3273 | goto out_path; |
| 3271 | 3274 | } |
| 3272 | 3275 | |
| 3276 | + depth = ext_depth(inode); | |
| 3277 | + ex = path[depth].p_ext; | |
| 3278 | + if (!ex) { | |
| 3279 | + EXT4_ERROR_INODE(inode, | |
| 3280 | + "bad extent address lblock: %lu, depth: %d pblock %llu", | |
| 3281 | + (unsigned long)ee_block, depth, path[depth].p_block); | |
| 3282 | + err = -EFSCORRUPTED; | |
| 3283 | + goto out; | |
| 3284 | + } | |
| 3285 | + | |
| 3273 | 3286 | err = ext4_ext_get_access(handle, inode, path + depth); |
| 3274 | 3287 | if (err) |
| 3275 | 3288 | goto out; |
| 3276 | 3289 | |
| 3277 | − depth = ext_depth(inode); | |
| 3278 | − ex = path[depth].p_ext; | |
| 3279 | − | |
| 3280 | 3290 | fix_extent_len: |
| 3281 | 3291 | ex->ee_len = orig_ex.ee_len; |
| 3282 | 3292 | err = ext4_ext_dirty(handle, inode, path + path->p_depth); |