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