1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Cryptographic API.
4  *
5  * Deflate algorithm (RFC 1951), implemented here primarily for use
6  * by IPCOMP (RFC 3173 & RFC 2394).
7  *
8  * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
9  * Copyright (c) 2023 Google, LLC. <ardb@kernel.org>
10  * Copyright (c) 2025 Herbert Xu <herbert@gondor.apana.org.au>
11  */
12 #include <crypto/internal/acompress.h>
13 #include <crypto/scatterwalk.h>
14 #include <linux/init.h>
15 #include <linux/kernel.h>
16 #include <linux/module.h>
17 #include <linux/mutex.h>
18 #include <linux/percpu.h>
19 #include <linux/scatterlist.h>
20 #include <linux/slab.h>
21 #include <linux/spinlock.h>
22 #include <linux/zlib.h>
23 
24 #define DEFLATE_DEF_LEVEL		Z_DEFAULT_COMPRESSION
25 #define DEFLATE_DEF_WINBITS		11
26 #define DEFLATE_DEF_MEMLEVEL		MAX_MEM_LEVEL
27 
28 struct deflate_stream {
29 	struct z_stream_s stream;
30 	u8 workspace[];
31 };
32 
33 static DEFINE_MUTEX(deflate_stream_lock);
34 
35 static void *deflate_alloc_stream(void)
36 {
37 	size_t size = max(zlib_inflate_workspacesize(),
38 			  zlib_deflate_workspacesize(-DEFLATE_DEF_WINBITS,
39 						     DEFLATE_DEF_MEMLEVEL));
40 	struct deflate_stream *ctx;
41 
42 	ctx = kvmalloc(sizeof(*ctx) + size, GFP_KERNEL);
43 	if (!ctx)
44 		return ERR_PTR(-ENOMEM);
45 
46 	ctx->stream.workspace = ctx->workspace;
47 
48 	return ctx;
49 }
50 
51 static struct crypto_acomp_streams deflate_streams = {
52 	.alloc_ctx = deflate_alloc_stream,
53 	.cfree_ctx = kvfree,
54 };
55 
56 static int deflate_compress_one(struct acomp_req *req,
57 				struct deflate_stream *ds)
58 {
59 	struct z_stream_s *stream = &ds->stream;
60 	struct acomp_walk walk;
61 	int ret;
62 
63 	ret = acomp_walk_virt(&walk, req, true);
64 	if (ret)
65 		return ret;
66 
67 	do {
68 		unsigned int dcur;
69 
70 		dcur = acomp_walk_next_dst(&walk);
71 		if (!dcur)
72 			return -ENOSPC;
73 
74 		stream->avail_out = dcur;
75 		stream->next_out = walk.dst.virt.addr;
76 
77 		do {
78 			int flush = Z_FINISH;
79 			unsigned int scur;
80 
81 			stream->avail_in = 0;
82 			stream->next_in = NULL;
83 
84 			scur = acomp_walk_next_src(&walk);
85 			if (scur) {
86 				if (acomp_walk_more_src(&walk, scur))
87 					flush = Z_NO_FLUSH;
88 				stream->avail_in = scur;
89 				stream->next_in = walk.src.virt.addr;
90 			}
91 
92 			ret = zlib_deflate(stream, flush);
93 
94 			if (scur) {
95 				scur -= stream->avail_in;
96 				acomp_walk_done_src(&walk, scur);
97 			}
98 		} while (ret == Z_OK && stream->avail_out);
99 
100 		acomp_walk_done_dst(&walk, dcur);
101 	} while (ret == Z_OK);
102 
103 	if (ret != Z_STREAM_END)
104 		return -EINVAL;
105 
106 	req->dlen = stream->total_out;
107 	return 0;
108 }
109 
110 static int deflate_compress(struct acomp_req *req)
111 {
112 	struct crypto_acomp_stream *s;
113 	struct deflate_stream *ds;
114 	int err;
115 
116 	s = crypto_acomp_lock_stream_bh(&deflate_streams);
117 	ds = s->ctx;
118 
119 	err = zlib_deflateInit2(&ds->stream, DEFLATE_DEF_LEVEL, Z_DEFLATED,
120 				-DEFLATE_DEF_WINBITS, DEFLATE_DEF_MEMLEVEL,
121 				Z_DEFAULT_STRATEGY);
122 	if (err != Z_OK) {
123 		err = -EINVAL;
124 		goto out;
125 	}
126 
127 	err = deflate_compress_one(req, ds);
128 
129 out:
130 	crypto_acomp_unlock_stream_bh(s);
131 
132 	return err;
133 }
134 
135 static int deflate_decompress_one(struct acomp_req *req,
136 				  struct deflate_stream *ds)
137 {
138 	struct z_stream_s *stream = &ds->stream;
139 	bool out_of_space = false;
140 	struct acomp_walk walk;
141 	int ret;
142 
143 	ret = acomp_walk_virt(&walk, req, true);
144 	if (ret)
145 		return ret;
146 
147 	do {
148 		unsigned int scur;
149 
150 		stream->avail_in = 0;
151 		stream->next_in = NULL;
152 
153 		scur = acomp_walk_next_src(&walk);
154 		if (scur) {
155 			stream->avail_in = scur;
156 			stream->next_in = walk.src.virt.addr;
157 		}
158 
159 		do {
160 			unsigned int dcur;
161 
162 			dcur = acomp_walk_next_dst(&walk);
163 			if (!dcur) {
164 				out_of_space = true;
165 				break;
166 			}
167 
168 			stream->avail_out = dcur;
169 			stream->next_out = walk.dst.virt.addr;
170 
171 			ret = zlib_inflate(stream, Z_NO_FLUSH);
172 
173 			dcur -= stream->avail_out;
174 			acomp_walk_done_dst(&walk, dcur);
175 		} while (ret == Z_OK && stream->avail_in);
176 
177 		if (scur)
178 			acomp_walk_done_src(&walk, scur);
179 
180 		if (out_of_space)
181 			return -ENOSPC;
182 	} while (ret == Z_OK);
183 
184 	if (ret != Z_STREAM_END)
185 		return -EINVAL;
186 
187 	req->dlen = stream->total_out;
188 	return 0;
189 }
190 
191 static int deflate_decompress(struct acomp_req *req)
192 {
193 	struct crypto_acomp_stream *s;
194 	struct deflate_stream *ds;
195 	int err;
196 
197 	s = crypto_acomp_lock_stream_bh(&deflate_streams);
198 	ds = s->ctx;
199 
200 	err = zlib_inflateInit2(&ds->stream, -DEFLATE_DEF_WINBITS);
201 	if (err != Z_OK) {
202 		err = -EINVAL;
203 		goto out;
204 	}
205 
206 	err = deflate_decompress_one(req, ds);
207 
208 out:
209 	crypto_acomp_unlock_stream_bh(s);
210 
211 	return err;
212 }
213 
214 static int deflate_init(struct crypto_acomp *tfm)
215 {
216 	int ret;
217 
218 	mutex_lock(&deflate_stream_lock);
219 	ret = crypto_acomp_alloc_streams(&deflate_streams);
220 	mutex_unlock(&deflate_stream_lock);
221 
222 	return ret;
223 }
224 
225 static struct acomp_alg acomp = {
226 	.compress		= deflate_compress,
227 	.decompress		= deflate_decompress,
228 	.init			= deflate_init,
229 	.base.cra_name		= "deflate",
230 	.base.cra_driver_name	= "deflate-generic",
231 	.base.cra_flags		= CRYPTO_ALG_REQ_VIRT,
232 	.base.cra_module	= THIS_MODULE,
233 };
234 
235 static int __init deflate_mod_init(void)
236 {
237 	return crypto_register_acomp(&acomp);
238 }
239 
240 static void __exit deflate_mod_fini(void)
241 {
242 	crypto_unregister_acomp(&acomp);
243 	crypto_acomp_free_streams(&deflate_streams);
244 }
245 
246 module_init(deflate_mod_init);
247 module_exit(deflate_mod_fini);
248 
249 MODULE_LICENSE("GPL");
250 MODULE_DESCRIPTION("Deflate Compression Algorithm for IPCOMP");
251 MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
252 MODULE_AUTHOR("Ard Biesheuvel <ardb@kernel.org>");
253 MODULE_AUTHOR("Herbert Xu <herbert@gondor.apana.org.au>");
254 MODULE_ALIAS_CRYPTO("deflate");
255 MODULE_ALIAS_CRYPTO("deflate-generic");
256