Skip to content

Commit 4c22171

Browse files
ematsumiyasmfrench
authored andcommitted
smb: client: compress: fix buffer overrun in lz77_compress()
@dst buffer is allocated with same size as @src, which, for good compression cases, works fine. However, when compression goes bad (e.g. random bytes payloads), the compressed size can increase significantly, and even by stopping the main loop at 7/8 of @slen, writing leftover literals could write past the end of @dst because of LZ77 metadata. To fix this, add lz77_compressed_alloc_size() helper to compute the correct allocation size for @dst, accounting for metadata and worst cast scenario (all literals). While this is overprovisioning memory, it's not only correct, but also allows lz77_compress() main loop to run without ever checking @dst limits (i.e. a perf improvement). Signed-off-by: Enzo Matsumiya <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent a55a608 commit 4c22171

3 files changed

Lines changed: 33 additions & 15 deletions

File tree

fs/smb/client/compress.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,7 @@ int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_s
329329
goto err_free;
330330
}
331331

332-
/*
333-
* This is just overprovisioning, as the algorithm will error out if @dst reaches 7/8
334-
* of @slen.
335-
*/
336-
dlen = slen;
332+
dlen = lz77_compressed_alloc_size(slen);
337333
dst = kvzalloc(dlen, GFP_KERNEL);
338334
if (!dst) {
339335
ret = -ENOMEM;

fs/smb/client/compress/lz77.c

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
137137
long flag = 0;
138138
u64 *htable;
139139

140+
/* This is probably a bug, so throw a warning. */
141+
if (WARN_ON_ONCE(*dlen < lz77_compressed_alloc_size(slen)))
142+
return -EINVAL;
143+
140144
srcp = src;
141145
end = src + slen;
142146
dstp = dst;
@@ -180,15 +184,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
180184
continue;
181185
}
182186

183-
/*
184-
* Bail out if @dstp reached >= 7/8 of @slen -- already compressed badly, not worth
185-
* going further.
186-
*/
187-
if (unlikely(dstp - dst >= slen - (slen >> 3))) {
188-
*dlen = slen;
189-
goto out;
190-
}
191-
192187
dstp = lz77_write_match(dstp, &nib, dist, len);
193188
srcp += len;
194189

@@ -225,7 +220,6 @@ noinline int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen)
225220
lz77_write32(flag_pos, flag);
226221

227222
*dlen = dstp - dst;
228-
out:
229223
kvfree(htable);
230224

231225
if (*dlen < slen)

fs/smb/client/compress/lz77.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,33 @@
1111

1212
#include <linux/kernel.h>
1313

14+
/**
15+
* lz77_compressed_alloc_size() - Compute compressed buffer size.
16+
* @size: uncompressed (src) size
17+
*
18+
* Compute allocation size for the compressed buffer based on uncompressed size.
19+
* Accounts for metadata and overprovision for the worst case scenario.
20+
*
21+
* LZ77 metadata is a 4-byte flag that is written:
22+
* - on dst begin (pos 0)
23+
* - every 32 literals or matches
24+
* - on end-of-stream (possibly, if last write was another flag)
25+
*
26+
* Worst case scenario is an all-literal compression, which means:
27+
* metadata bytes = 4 + ((@size / 32) * 4) + 4, or, simplified, (@size >> 3) + 8
28+
*
29+
* The worst case scenario rarely happens, but such overprovisioning also allows lz77_compress()
30+
* main loop to run without ever bound checking dst, which is a huge perf improvement, while also
31+
* being safe when compression goes bad.
32+
*
33+
* Return: required (*) allocation size for compressed buffer.
34+
*
35+
* (*) checked once in the beginning of lz77_compress()
36+
*/
37+
static __always_inline u32 lz77_compressed_alloc_size(const u32 size)
38+
{
39+
return size + (size >> 3) + 8;
40+
}
41+
1442
int lz77_compress(const void *src, u32 slen, void *dst, u32 *dlen);
1543
#endif /* _SMB_COMPRESS_LZ77_H */

0 commit comments

Comments
 (0)