diff options
| author | Enzo Matsumiya <ematsumiya@suse.de> | 2025-12-01 13:14:14 -0300 |
|---|---|---|
| committer | Enzo Matsumiya <ematsumiya@suse.de> | 2025-12-05 11:44:46 -0300 |
| commit | 20823ef36a8ce02f84790543d399daf1fd48006a (patch) | |
| tree | 562678e282f2d3a4ffd262ba82181f4b5e162c01 | |
| parent | 8ef77e98f9e2a4af01fbcd7f8ff09682e10554a7 (diff) | |
| download | linux-20823ef36a8ce02f84790543d399daf1fd48006a.tar.gz linux-20823ef36a8ce02f84790543d399daf1fd48006a.tar.bz2 linux-20823ef36a8ce02f84790543d399daf1fd48006a.zip | |
smb: client: compress: fix compressing too many literals
When compressing too many literals (e.g. a chunk with random data only),
we might hit the compressed buffer limit.
This is because @dst is allocated with @slen, without accounting for
LZ77 metadata.
To fix this, add lz77_calc_dlen() helper that will compute the required
@dst size, accounting for metadata and the worst case scenario
(all literals).
It's overprovisioning, but besides safer (and correct), this also allows
us to run through @dst in lz77_compress() without ever checking its
bounds.
Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
| -rw-r--r-- | fs/smb/client/compress.c | 6 | ||||
| -rw-r--r-- | fs/smb/client/compress/lz77.c | 37 | ||||
| -rw-r--r-- | fs/smb/client/compress/lz77.h | 1 |
3 files changed, 28 insertions, 16 deletions
diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c index db709f5cd2e1..db18c93f8ee8 100644 --- a/fs/smb/client/compress.c +++ b/fs/smb/client/compress.c @@ -314,11 +314,7 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s goto err_free; } - /* - * This is just overprovisioning, as the algorithm will error out if @dst reaches 7/8 - * of @slen. - */ - dlen = slen; + dlen = lz77_calc_dlen(slen); dst = kvzalloc(dlen, GFP_KERNEL); if (!dst) { ret = -ENOMEM; diff --git a/fs/smb/client/compress/lz77.c b/fs/smb/client/compress/lz77.c index 80c57175769d..1d0cb4a22f5f 100644 --- a/fs/smb/client/compress/lz77.c +++ b/fs/smb/client/compress/lz77.c @@ -131,6 +131,26 @@ static __always_inline void *lz77_write_match(void *dst, void **nib, u32 dist, u return dst + 4; } +/* + * Compute compressed (dst) buffer length based on uncompressed (src) length. + * + * Accounts for metadata and overprovision for the worst case scenario. + * + * Metadata is a 4-byte flag. + * A flag is written: + * - on dst begin (pos 0) + * - when flag count is 32 (1 literal or 1 match increments counter) + * - on end-of-stream (end of dst) + * + * Worst case scenario is an all-literal compression, this means: + * metadata_bytes = 4 + ((@slen / 32) * 4) + 4, or reduced (@slen >> 3) + 8 + * -> @slen + metadata_bytes + */ +u32 lz77_calc_dlen(u32 slen) +{ + return slen + (slen >> 3) + 8; +} + noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen) { const void *srcp, *end; @@ -150,7 +170,12 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen) if (!htable) return -ENOMEM; - /* Main loop. */ + /* + * Main loop. + * + * Assuming @dlen was computed with lz77_calc_dlen(), we can run without bound checking + * @dst. + */ do { u32 dist, len = 0; const void *wnd; @@ -182,15 +207,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen) continue; } - /* - * Bail out if @dstp reached >= 7/8 of @slen -- already compressed badly, not worth - * going further. - */ - if (unlikely(dstp - dst >= slen - (slen >> 3))) { - *dlen = slen; - goto out; - } - dstp = lz77_write_match(dstp, &nib, dist, len); srcp += len; @@ -227,7 +243,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen) lz77_write32(flag_pos, flag); *dlen = dstp - dst; -out: kvfree(htable); if (*dlen < slen) diff --git a/fs/smb/client/compress/lz77.h b/fs/smb/client/compress/lz77.h index cdcb191b48a2..3c75b70b51b0 100644 --- a/fs/smb/client/compress/lz77.h +++ b/fs/smb/client/compress/lz77.h @@ -11,5 +11,6 @@ #include <linux/kernel.h> +u32 lz77_calc_dlen(u32 slen); int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen); #endif /* _SMB_COMPRESS_LZ77_H */ |
