erofs-utils: lib: switch to on-heap fitblk_buffer for libzstd

 - Allocating VLAs on the stack (or using alloca()) for large sizes
   could exceed the stack limit;

 - It's easier to isolate these buffers on the heap for code sanitizers
   to detect potential bugs.

Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
Link: https://lore.kernel.org/r/20250703053446.201941-1-hsiangkao@linux.alibaba.com
diff --git a/lib/compressor_libzstd.c b/lib/compressor_libzstd.c
index 223806e..de04c58 100644
--- a/lib/compressor_libzstd.c
+++ b/lib/compressor_libzstd.c
@@ -4,20 +4,34 @@
 #include "erofs/config.h"
 #include <zstd.h>
 #include <zstd_errors.h>
-#include <alloca.h>
+#include <stdlib.h>
 #include "compressor.h"
 #include "erofs/atomic.h"
 
+struct erofs_libzstd_context {
+	ZSTD_CCtx *cctx;
+	u8 *fitblk_buffer;
+	unsigned int fitblk_bufsiz;
+};
+
 static int libzstd_compress_destsize(const struct erofs_compress *c,
 				     const void *src, unsigned int *srcsize,
 				     void *dst, unsigned int dstsize)
 {
-	ZSTD_CCtx *cctx = c->private_data;
+	struct erofs_libzstd_context *ctx = c->private_data;
 	size_t l = 0;		/* largest input that fits so far */
 	size_t l_csize = 0;
 	size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */
 	size_t m;
-	u8 *fitblk_buffer = alloca(dstsize + 32);
+
+	if (dstsize + 32 > ctx->fitblk_bufsiz) {
+		u8 *buf = realloc(ctx->fitblk_buffer, dstsize + 32);
+
+		if (!buf)
+			return -ENOMEM;
+		ctx->fitblk_bufsiz = dstsize + 32;
+		ctx->fitblk_buffer = buf;
+	}
 
 	m = dstsize * 4;
 	for (;;) {
@@ -26,7 +40,7 @@
 		m = max(m, l + 1);
 		m = min(m, r - 1);
 
-		csize = ZSTD_compress2(cctx, fitblk_buffer,
+		csize = ZSTD_compress2(ctx->cctx, ctx->fitblk_buffer,
 				       dstsize + 32, src, m);
 		if (ZSTD_isError(csize)) {
 			if (ZSTD_getErrorCode(csize) == ZSTD_error_dstSize_tooSmall)
@@ -36,7 +50,7 @@
 
 		if (csize > 0 && csize <= dstsize) {
 			/* Fits */
-			memcpy(dst, fitblk_buffer, csize);
+			memcpy(dst, ctx->fitblk_buffer, csize);
 			l = m;
 			l_csize = csize;
 			if (r <= l + 1 || csize + 1 >= dstsize)
@@ -61,9 +75,14 @@
 
 static int compressor_libzstd_exit(struct erofs_compress *c)
 {
-	if (!c->private_data)
+	struct erofs_libzstd_context *ctx = c->private_data;
+
+	if (!ctx)
 		return -EINVAL;
-	ZSTD_freeCCtx(c->private_data);
+
+	free(ctx->fitblk_buffer);
+	ZSTD_freeCCtx(ctx->cctx);
+	free(ctx);
 	return 0;
 }
 
@@ -101,27 +120,41 @@
 
 static int compressor_libzstd_init(struct erofs_compress *c)
 {
+	struct erofs_libzstd_context *ctx = c->private_data;
 	static erofs_atomic_bool_t __warnonce;
-	ZSTD_CCtx *cctx = c->private_data;
-	size_t err;
+	ZSTD_CCtx *cctx;
+	size_t errcode;
+	int err;
 
-	ZSTD_freeCCtx(cctx);
+	if (ctx) {
+		ZSTD_freeCCtx(ctx->cctx);
+		ctx->cctx = NULL;
+		c->private_data = NULL;
+	} else {
+		ctx = calloc(1, sizeof(*ctx));
+		if (!ctx)
+			return -ENOMEM;
+	}
 	cctx = ZSTD_createCCtx();
-	if (!cctx)
-		return -ENOMEM;
+	if (!cctx) {
+		err = -ENOMEM;
+		goto out_err;
+	}
 
-	err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, c->compression_level);
-	if (ZSTD_isError(err)) {
+	err = -EINVAL;
+	errcode = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, c->compression_level);
+	if (ZSTD_isError(errcode)) {
 		erofs_err("failed to set compression level: %s",
-			  ZSTD_getErrorName(err));
-		return -EINVAL;
+			  ZSTD_getErrorName(errcode));
+		goto out_err;
 	}
-	err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, ilog2(c->dict_size));
-	if (ZSTD_isError(err)) {
-		erofs_err("failed to set window log: %s", ZSTD_getErrorName(err));
-		return -EINVAL;
+	errcode = ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, ilog2(c->dict_size));
+	if (ZSTD_isError(errcode)) {
+		erofs_err("failed to set window log: %s", ZSTD_getErrorName(errcode));
+		goto out_err;
 	}
-	c->private_data = cctx;
+	ctx->cctx = cctx;
+	c->private_data = ctx;
 
 	if (!erofs_atomic_test_and_set(&__warnonce)) {
 		erofs_warn("EXPERIMENTAL libzstd compressor in use. Note that `fitblk` isn't supported by upstream zstd for now.");
@@ -129,6 +162,10 @@
 		erofs_info("You could clarify further needs in zstd repository <https://github.com/facebook/zstd/issues> for reference too.");
 	}
 	return 0;
+out_err:
+	ZSTD_freeCCtx(cctx);
+	free(ctx);
+	return err;
 }
 
 const struct erofs_compressor erofs_compressor_libzstd = {