// SPDX-License-Identifier: GPL-2.0-or-later /* * Cryptographic API. * * Deflate algorithm (RFC 1951), implemented here primarily for use * by IPCOMP (RFC 3173 & RFC 2394). * * Copyright (c) 2003 James Morris * Copyright (c) 2023 Google, LLC. * Copyright (c) 2025 Herbert Xu */ #include #include #include #include #include #include #include #include #include #include #include #define DEFLATE_DEF_LEVEL Z_DEFAULT_COMPRESSION #define DEFLATE_DEF_WINBITS 11 #define DEFLATE_DEF_MEMLEVEL MAX_MEM_LEVEL struct deflate_stream { struct z_stream_s stream; u8 workspace[]; }; static DEFINE_MUTEX(deflate_stream_lock); static void *deflate_alloc_stream(void) { size_t size = max(zlib_inflate_workspacesize(), zlib_deflate_workspacesize(-DEFLATE_DEF_WINBITS, DEFLATE_DEF_MEMLEVEL)); struct deflate_stream *ctx; ctx = kvmalloc(sizeof(*ctx) + size, GFP_KERNEL); if (!ctx) return ERR_PTR(-ENOMEM); ctx->stream.workspace = ctx->workspace; return ctx; } static struct crypto_acomp_streams deflate_streams = { .alloc_ctx = deflate_alloc_stream, .cfree_ctx = kvfree, }; static int deflate_compress_one(struct acomp_req *req, struct deflate_stream *ds) { struct z_stream_s *stream = &ds->stream; struct acomp_walk walk; int ret; ret = acomp_walk_virt(&walk, req, true); if (ret) return ret; do { unsigned int dcur; dcur = acomp_walk_next_dst(&walk); if (!dcur) return -ENOSPC; stream->avail_out = dcur; stream->next_out = walk.dst.virt.addr; do { int flush = Z_FINISH; unsigned int scur; stream->avail_in = 0; stream->next_in = NULL; scur = acomp_walk_next_src(&walk); if (scur) { if (acomp_walk_more_src(&walk, scur)) flush = Z_NO_FLUSH; stream->avail_in = scur; stream->next_in = walk.src.virt.addr; } ret = zlib_deflate(stream, flush); if (scur) { scur -= stream->avail_in; acomp_walk_done_src(&walk, scur); } } while (ret == Z_OK && stream->avail_out); acomp_walk_done_dst(&walk, dcur); } while (ret == Z_OK); if (ret != Z_STREAM_END) return -EINVAL; req->dlen = stream->total_out; return 0; } static int deflate_compress(struct acomp_req *req) { struct crypto_acomp_stream *s; struct deflate_stream *ds; int err; s = crypto_acomp_lock_stream_bh(&deflate_streams); ds = s->ctx; err = zlib_deflateInit2(&ds->stream, DEFLATE_DEF_LEVEL, Z_DEFLATED, -DEFLATE_DEF_WINBITS, DEFLATE_DEF_MEMLEVEL, Z_DEFAULT_STRATEGY); if (err != Z_OK) { err = -EINVAL; goto out; } err = deflate_compress_one(req, ds); out: crypto_acomp_unlock_stream_bh(s); return err; } static int deflate_decompress_one(struct acomp_req *req, struct deflate_stream *ds) { struct z_stream_s *stream = &ds->stream; bool out_of_space = false; struct acomp_walk walk; int ret; ret = acomp_walk_virt(&walk, req, true); if (ret) return ret; do { unsigned int scur; stream->avail_in = 0; stream->next_in = NULL; scur = acomp_walk_next_src(&walk); if (scur) { stream->avail_in = scur; stream->next_in = walk.src.virt.addr; } do { unsigned int dcur; dcur = acomp_walk_next_dst(&walk); if (!dcur) { out_of_space = true; break; } stream->avail_out = dcur; stream->next_out = walk.dst.virt.addr; ret = zlib_inflate(stream, Z_NO_FLUSH); dcur -= stream->avail_out; acomp_walk_done_dst(&walk, dcur); } while (ret == Z_OK && stream->avail_in); if (scur) acomp_walk_done_src(&walk, scur); if (out_of_space) return -ENOSPC; } while (ret == Z_OK); if (ret != Z_STREAM_END) return -EINVAL; req->dlen = stream->total_out; return 0; } static int deflate_decompress(struct acomp_req *req) { struct crypto_acomp_stream *s; struct deflate_stream *ds; int err; s = crypto_acomp_lock_stream_bh(&deflate_streams); ds = s->ctx; err = zlib_inflateInit2(&ds->stream, -DEFLATE_DEF_WINBITS); if (err != Z_OK) { err = -EINVAL; goto out; } err = deflate_decompress_one(req, ds); out: crypto_acomp_unlock_stream_bh(s); return err; } static int deflate_init(struct crypto_acomp *tfm) { int ret; mutex_lock(&deflate_stream_lock); ret = crypto_acomp_alloc_streams(&deflate_streams); mutex_unlock(&deflate_stream_lock); return ret; } static struct acomp_alg acomp = { .compress = deflate_compress, .decompress = deflate_decompress, .init = deflate_init, .base.cra_name = "deflate", .base.cra_driver_name = "deflate-generic", .base.cra_flags = CRYPTO_ALG_REQ_VIRT, .base.cra_module = THIS_MODULE, }; static int __init deflate_mod_init(void) { return crypto_register_acomp(&acomp); } static void __exit deflate_mod_fini(void) { crypto_unregister_acomp(&acomp); crypto_acomp_free_streams(&deflate_streams); } module_init(deflate_mod_init); module_exit(deflate_mod_fini); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Deflate Compression Algorithm for IPCOMP"); MODULE_AUTHOR("James Morris "); MODULE_AUTHOR("Ard Biesheuvel "); MODULE_AUTHOR("Herbert Xu "); MODULE_ALIAS_CRYPTO("deflate"); MODULE_ALIAS_CRYPTO("deflate-generic");