[BUG] erofs: LZMA stream state NULL deref after failed dict grow

Qingyu Zhang posted 1 patch 3 weeks, 3 days ago
[BUG] erofs: LZMA stream state NULL deref after failed dict grow
Posted by Qingyu Zhang 3 weeks, 3 days ago
Hello,

z_erofs_load_lzma_config() can leave strm->state == NULL while still
publishing z_erofs_lzma_max_dictsize. The next mount of the same
image skips realloc and decompress NULL-derefs.

Type: null-pointer dereference

* Summary

       for (strm = head; strm; strm = strm->next) {
               if (strm->state)
                       xz_dec_microlzma_end(strm->state);
               strm->state = xz_dec_microlzma_alloc(..., dict_size);
               if (!strm->state)
                       err = -ENOMEM;
       }
       ...
       z_erofs_lzma_max_dictsize = dict_size;  /* even if err */

On alloc failure the old state is already gone. The next mount sees
max_dictsize >= dict_size and returns 0 without retrying. Then:

       xz_dec_microlzma_reset(strm->state, ...);  /* state is NULL */

* Affected

622ceaddb764. Needs CONFIG_EROFS_FS_ZIP_LZMA, a microlzma image,
and a failed xz_dec_microlzma_alloc (fault injection or memory
pressure). KASAN recommended.

* Reproduction

1. mkfs.erofs -z lzma,dictsize=4096 lzma.img dir/
2. Force xz_dec_microlzma_alloc() to fail on first mount
  (poc/fail_lzma.c: kprobe sets dict_size=0).
3. First mount returns -ENOMEM.
4. Disarm the probe, mount the same image again (succeeds via the
  max_dictsize early-out).
5. cat /mnt/payload

KASAN: null-ptr-deref in xz_dec_microlzma_reset from
z_erofs_lzma_decompress.

PoC: poc/run.sh. Use lzma_streams=1 to keep the pool to one stream.

* Expected

Failed grow keeps the previous xz state (or retries next mount) and
does not bump z_erofs_lzma_max_dictsize.

* Actual

NULL state + published max_dictsize -> NPD on the next read.

Please consider the suggested patch.

Thanks.

Suggested patch
```
diff --git a/fs/erofs/decompressor_lzma.c b/fs/erofs/decompressor_lzma.c
index 6b0cdb446c6a..3719c6f83f8a 100644
--- a/fs/erofs/decompressor_lzma.c
+++ b/fs/erofs/decompressor_lzma.c
@@ -128,11 +128,16 @@ static int z_erofs_load_lzma_config(struct
super_block *sb,
    err = 0;
    /* 2. walk each isolated stream and grow max dict_size if needed */
    for (strm = head; strm; strm = strm->next) {
+        struct xz_dec_microlzma *state;
+
+        state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size);
+        if (!state) {
+            err = -ENOMEM;
+            continue;
+        }
        if (strm->state)
            xz_dec_microlzma_end(strm->state);
-        strm->state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size);
-        if (!strm->state)
-            err = -ENOMEM;
+        strm->state = state;
    }

    /* 3. push back all to the global list and update max dict_size */
@@ -142,7 +147,8 @@ static int z_erofs_load_lzma_config(struct super_block *sb,
    spin_unlock(&z_erofs_lzma_lock);
    wake_up_all(&z_erofs_lzma_wq);

-    z_erofs_lzma_max_dictsize = dict_size;
+    if (!err)
+        z_erofs_lzma_max_dictsize = dict_size;
    mutex_unlock(&lzma_resize_mutex);
    return err;
}
```
Re: [BUG] erofs: LZMA stream state NULL deref after failed dict grow
Posted by Gao Xiang 3 weeks, 3 days ago
Hi,

On Wed, Sep 02, 2026 at 10:04:41AM +0800, Qingyu Zhang wrote:
> Hello,
> 
> z_erofs_load_lzma_config() can leave strm->state == NULL while still
> publishing z_erofs_lzma_max_dictsize. The next mount of the same
> image skips realloc and decompress NULL-derefs.
> 
> Type: null-pointer dereference
> 
> * Summary
> 
>        for (strm = head; strm; strm = strm->next) {
>                if (strm->state)
>                        xz_dec_microlzma_end(strm->state);
>                strm->state = xz_dec_microlzma_alloc(..., dict_size);
>                if (!strm->state)
>                        err = -ENOMEM;
>        }
>        ...
>        z_erofs_lzma_max_dictsize = dict_size;  /* even if err */
> 
> On alloc failure the old state is already gone. The next mount sees
> max_dictsize >= dict_size and returns 0 without retrying. Then:
> 
>        xz_dec_microlzma_reset(strm->state, ...);  /* state is NULL */
> 

Actually there were similar reports:
https://sashiko.dev/#/patchset/20260714114729.3760594-1-michael.bommarito%40gmail.com
https://lore.kernel.org/r/20260830104109.60639-1-nikhilgurudasani314@gmail.com

I will reply Nikhil's patch soon.

Thanks,
Gao Xiang