264 lines · 5.5 KB · C
RawDownload
// 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 <jmorris@intercode.com.au> * Copyright (c) 2023 Google, LLC. <ardb@kernel.org> * Copyright (c) 2025 Herbert Xu <herbert@gondor.apana.org.au> */#include <crypto/internal/acompress.h>#include <crypto/scatterwalk.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/overflow.h>#include <linux/percpu.h>#include <linux/scatterlist.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/zlib.h> #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_flex(*ctx, workspace, size);	if (!ctx)		return ERR_PTR(-ENOMEM); 	ctx->stream.workspace = ctx->workspace; 	return ctx;} static void deflate_free_stream(void *ctx){	kvfree(ctx);} static struct crypto_acomp_streams deflate_streams = {	.alloc_ctx = deflate_alloc_stream,	.free_ctx = deflate_free_stream,}; 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;			unsigned long avail_in; 			dcur = acomp_walk_next_dst(&walk); 			stream->avail_out = dcur;			stream->next_out = walk.dst.virt.addr;			avail_in = stream->avail_in; 			ret = zlib_inflate(stream, Z_NO_FLUSH); 			if (!dcur && avail_in == stream->avail_in) {				out_of_space = true;				break;			} 			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 <jmorris@intercode.com.au>");MODULE_AUTHOR("Ard Biesheuvel <ardb@kernel.org>");MODULE_AUTHOR("Herbert Xu <herbert@gondor.apana.org.au>");MODULE_ALIAS_CRYPTO("deflate");MODULE_ALIAS_CRYPTO("deflate-generic");