xref: /aosp_15_r20/external/kmod/libkmod/libkmod-file.c (revision cc4ad7da8cefe208cb129ac2aa9a357c7c72deb2)
1 /*
2  * libkmod - interface to kernel module operations
3  *
4  * Copyright (C) 2011-2013  ProFUSION embedded systems
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <errno.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/mman.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #ifdef ENABLE_ZSTD
30 #include <zstd.h>
31 #endif
32 #ifdef ENABLE_XZ
33 #include <lzma.h>
34 #endif
35 #ifdef ENABLE_ZLIB
36 #include <zlib.h>
37 #endif
38 
39 #include <shared/util.h>
40 
41 #include "libkmod.h"
42 #include "libkmod-internal.h"
43 
44 struct kmod_file;
45 struct file_ops {
46 	int (*load)(struct kmod_file *file);
47 	void (*unload)(struct kmod_file *file);
48 };
49 
50 struct kmod_file {
51 #ifdef ENABLE_ZSTD
52 	bool zstd_used;
53 #endif
54 #ifdef ENABLE_XZ
55 	bool xz_used;
56 #endif
57 #ifdef ENABLE_ZLIB
58 	gzFile gzf;
59 #endif
60 	int fd;
61 	enum kmod_file_compression_type compression;
62 	off_t size;
63 	void *memory;
64 	const struct file_ops *ops;
65 	const struct kmod_ctx *ctx;
66 	struct kmod_elf *elf;
67 };
68 
69 #ifdef ENABLE_ZSTD
zstd_read_block(struct kmod_file * file,size_t block_size,ZSTD_inBuffer * input,size_t * input_capacity)70 static int zstd_read_block(struct kmod_file *file, size_t block_size,
71 			   ZSTD_inBuffer *input, size_t *input_capacity)
72 {
73 	ssize_t rdret;
74 	int ret;
75 
76 	if (*input_capacity < block_size) {
77 		free((void *)input->src);
78 		input->src = malloc(block_size);
79 		if (input->src == NULL) {
80 			ret = -errno;
81 			ERR(file->ctx, "zstd: %m\n");
82 			return ret;
83 		}
84 		*input_capacity = block_size;
85 	}
86 
87 	rdret = read(file->fd, (void *)input->src, block_size);
88 	if (rdret < 0) {
89 		ret = -errno;
90 		ERR(file->ctx, "zstd: %m\n");
91 		return ret;
92 	}
93 
94 	input->pos = 0;
95 	input->size = rdret;
96 	return 0;
97 }
98 
zstd_ensure_outbuffer_space(ZSTD_outBuffer * buffer,size_t min_free)99 static int zstd_ensure_outbuffer_space(ZSTD_outBuffer *buffer, size_t min_free)
100 {
101 	uint8_t *old_buffer = buffer->dst;
102 	int ret = 0;
103 
104 	if (buffer->size - buffer->pos >= min_free)
105 		return 0;
106 
107 	buffer->size += min_free;
108 	buffer->dst = realloc(buffer->dst, buffer->size);
109 	if (buffer->dst == NULL) {
110 		ret = -errno;
111 		free(old_buffer);
112 	}
113 
114 	return ret;
115 }
116 
zstd_decompress_block(struct kmod_file * file,ZSTD_DStream * dstr,ZSTD_inBuffer * input,ZSTD_outBuffer * output,size_t * next_block_size)117 static int zstd_decompress_block(struct kmod_file *file, ZSTD_DStream *dstr,
118 				 ZSTD_inBuffer *input, ZSTD_outBuffer *output,
119 				 size_t *next_block_size)
120 {
121 	size_t out_buf_min_size = ZSTD_DStreamOutSize();
122 	int ret = 0;
123 
124 	do {
125 		ssize_t dsret;
126 
127 		ret = zstd_ensure_outbuffer_space(output, out_buf_min_size);
128 		if (ret) {
129 			ERR(file->ctx, "zstd: %s\n", strerror(-ret));
130 			break;
131 		}
132 
133 		dsret = ZSTD_decompressStream(dstr, output, input);
134 		if (ZSTD_isError(dsret)) {
135 			ret = -EINVAL;
136 			ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(dsret));
137 			break;
138 		}
139 		if (dsret > 0)
140 			*next_block_size = (size_t)dsret;
141 	} while (input->pos < input->size
142 		 || output->pos > output->size
143 		 || output->size - output->pos < out_buf_min_size);
144 
145 	return ret;
146 }
147 
load_zstd(struct kmod_file * file)148 static int load_zstd(struct kmod_file *file)
149 {
150 	ZSTD_DStream *dstr;
151 	size_t next_block_size;
152 	size_t zst_inb_capacity = 0;
153 	ZSTD_inBuffer zst_inb = { 0 };
154 	ZSTD_outBuffer zst_outb = { 0 };
155 	int ret;
156 
157 	dstr = ZSTD_createDStream();
158 	if (dstr == NULL) {
159 		ret = -EINVAL;
160 		ERR(file->ctx, "zstd: Failed to create decompression stream\n");
161 		goto out;
162 	}
163 
164 	next_block_size = ZSTD_initDStream(dstr);
165 
166 	while (true) {
167 		ret = zstd_read_block(file, next_block_size, &zst_inb,
168 				      &zst_inb_capacity);
169 		if (ret != 0)
170 			goto out;
171 		if (zst_inb.size == 0) /* EOF */
172 			break;
173 
174 		ret = zstd_decompress_block(file, dstr, &zst_inb, &zst_outb,
175 					    &next_block_size);
176 		if (ret != 0)
177 			goto out;
178 	}
179 
180 	ZSTD_freeDStream(dstr);
181 	free((void *)zst_inb.src);
182 	file->zstd_used = true;
183 	file->memory = zst_outb.dst;
184 	file->size = zst_outb.pos;
185 	return 0;
186 out:
187 	if (dstr != NULL)
188 		ZSTD_freeDStream(dstr);
189 	free((void *)zst_inb.src);
190 	free((void *)zst_outb.dst);
191 	return ret;
192 }
193 
unload_zstd(struct kmod_file * file)194 static void unload_zstd(struct kmod_file *file)
195 {
196 	if (!file->zstd_used)
197 		return;
198 	free(file->memory);
199 }
200 
201 static const char magic_zstd[] = {0x28, 0xB5, 0x2F, 0xFD};
202 #endif
203 
204 #ifdef ENABLE_XZ
xz_uncompress_belch(struct kmod_file * file,lzma_ret ret)205 static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret)
206 {
207 	switch (ret) {
208 	case LZMA_MEM_ERROR:
209 		ERR(file->ctx, "xz: %s\n", strerror(ENOMEM));
210 		break;
211 	case LZMA_FORMAT_ERROR:
212 		ERR(file->ctx, "xz: File format not recognized\n");
213 		break;
214 	case LZMA_OPTIONS_ERROR:
215 		ERR(file->ctx, "xz: Unsupported compression options\n");
216 		break;
217 	case LZMA_DATA_ERROR:
218 		ERR(file->ctx, "xz: File is corrupt\n");
219 		break;
220 	case LZMA_BUF_ERROR:
221 		ERR(file->ctx, "xz: Unexpected end of input\n");
222 		break;
223 	default:
224 		ERR(file->ctx, "xz: Internal error (bug)\n");
225 		break;
226 	}
227 }
228 
xz_uncompress(lzma_stream * strm,struct kmod_file * file)229 static int xz_uncompress(lzma_stream *strm, struct kmod_file *file)
230 {
231 	uint8_t in_buf[BUFSIZ], out_buf[BUFSIZ];
232 	lzma_action action = LZMA_RUN;
233 	lzma_ret ret;
234 	void *p = NULL;
235 	size_t total = 0;
236 
237 	strm->avail_in  = 0;
238 	strm->next_out  = out_buf;
239 	strm->avail_out = sizeof(out_buf);
240 
241 	while (true) {
242 		if (strm->avail_in == 0) {
243 			ssize_t rdret = read(file->fd, in_buf, sizeof(in_buf));
244 			if (rdret < 0) {
245 				ret = -errno;
246 				goto out;
247 			}
248 			strm->next_in  = in_buf;
249 			strm->avail_in = rdret;
250 			if (rdret == 0)
251 				action = LZMA_FINISH;
252 		}
253 		ret = lzma_code(strm, action);
254 		if (strm->avail_out == 0 || ret != LZMA_OK) {
255 			size_t write_size = BUFSIZ - strm->avail_out;
256 			char *tmp = realloc(p, total + write_size);
257 			if (tmp == NULL) {
258 				ret = -errno;
259 				goto out;
260 			}
261 			memcpy(tmp + total, out_buf, write_size);
262 			total += write_size;
263 			p = tmp;
264 			strm->next_out = out_buf;
265 			strm->avail_out = BUFSIZ;
266 		}
267 		if (ret == LZMA_STREAM_END)
268 			break;
269 		if (ret != LZMA_OK) {
270 			xz_uncompress_belch(file, ret);
271 			ret = -EINVAL;
272 			goto out;
273 		}
274 	}
275 	file->xz_used = true;
276 	file->memory = p;
277 	file->size = total;
278 	return 0;
279  out:
280 	free(p);
281 	return ret;
282 }
283 
load_xz(struct kmod_file * file)284 static int load_xz(struct kmod_file *file)
285 {
286 	lzma_stream strm = LZMA_STREAM_INIT;
287 	lzma_ret lzret;
288 	int ret;
289 
290 	lzret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED);
291 	if (lzret == LZMA_MEM_ERROR) {
292 		ERR(file->ctx, "xz: %s\n", strerror(ENOMEM));
293 		return -ENOMEM;
294 	} else if (lzret != LZMA_OK) {
295 		ERR(file->ctx, "xz: Internal error (bug)\n");
296 		return -EINVAL;
297 	}
298 	ret = xz_uncompress(&strm, file);
299 	lzma_end(&strm);
300 	return ret;
301 }
302 
unload_xz(struct kmod_file * file)303 static void unload_xz(struct kmod_file *file)
304 {
305 	if (!file->xz_used)
306 		return;
307 	free(file->memory);
308 }
309 
310 static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0};
311 #endif
312 
313 #ifdef ENABLE_ZLIB
314 #define READ_STEP (4 * 1024 * 1024)
load_zlib(struct kmod_file * file)315 static int load_zlib(struct kmod_file *file)
316 {
317 	int err = 0;
318 	off_t did = 0, total = 0;
319 	_cleanup_free_ unsigned char *p = NULL;
320 
321 	errno = 0;
322 	file->gzf = gzdopen(file->fd, "rb");
323 	if (file->gzf == NULL)
324 		return -errno;
325 	file->fd = -1; /* now owned by gzf due gzdopen() */
326 
327 	for (;;) {
328 		int r;
329 
330 		if (did == total) {
331 			void *tmp = realloc(p, total + READ_STEP);
332 			if (tmp == NULL) {
333 				err = -errno;
334 				goto error;
335 			}
336 			total += READ_STEP;
337 			p = tmp;
338 		}
339 
340 		r = gzread(file->gzf, p + did, total - did);
341 		if (r == 0)
342 			break;
343 		else if (r < 0) {
344 			int gzerr;
345 			const char *gz_errmsg = gzerror(file->gzf, &gzerr);
346 
347 			ERR(file->ctx, "gzip: %s\n", gz_errmsg);
348 
349 			/* gzip might not set errno here */
350 			err = gzerr == Z_ERRNO ? -errno : -EINVAL;
351 			goto error;
352 		}
353 		did += r;
354 	}
355 
356 	file->memory = p;
357 	file->size = did;
358 	p = NULL;
359 	return 0;
360 
361 error:
362 	gzclose(file->gzf);
363 	return err;
364 }
365 
unload_zlib(struct kmod_file * file)366 static void unload_zlib(struct kmod_file *file)
367 {
368 	if (file->gzf == NULL)
369 		return;
370 	free(file->memory);
371 	gzclose(file->gzf); /* closes file->fd */
372 }
373 
374 static const char magic_zlib[] = {0x1f, 0x8b};
375 #endif
376 
377 static const struct comp_type {
378 	size_t magic_size;
379 	enum kmod_file_compression_type compression;
380 	const char *magic_bytes;
381 	const struct file_ops ops;
382 } comp_types[] = {
383 #ifdef ENABLE_ZSTD
384 	{sizeof(magic_zstd),	KMOD_FILE_COMPRESSION_ZSTD, magic_zstd, {load_zstd, unload_zstd}},
385 #endif
386 #ifdef ENABLE_XZ
387 	{sizeof(magic_xz),	KMOD_FILE_COMPRESSION_XZ, magic_xz, {load_xz, unload_xz}},
388 #endif
389 #ifdef ENABLE_ZLIB
390 	{sizeof(magic_zlib),	KMOD_FILE_COMPRESSION_ZLIB, magic_zlib, {load_zlib, unload_zlib}},
391 #endif
392 	{0,			KMOD_FILE_COMPRESSION_NONE, NULL, {NULL, NULL}}
393 };
394 
load_reg(struct kmod_file * file)395 static int load_reg(struct kmod_file *file)
396 {
397 	struct stat st;
398 
399 	if (fstat(file->fd, &st) < 0)
400 		return -errno;
401 
402 	file->size = st.st_size;
403 	file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE,
404 			    file->fd, 0);
405 	if (file->memory == MAP_FAILED)
406 		return -errno;
407 
408 	return 0;
409 }
410 
unload_reg(struct kmod_file * file)411 static void unload_reg(struct kmod_file *file)
412 {
413 	munmap(file->memory, file->size);
414 }
415 
416 static const struct file_ops reg_ops = {
417 	load_reg, unload_reg
418 };
419 
kmod_file_get_elf(struct kmod_file * file)420 struct kmod_elf *kmod_file_get_elf(struct kmod_file *file)
421 {
422 	if (file->elf)
423 		return file->elf;
424 
425 	kmod_file_load_contents(file);
426 	file->elf = kmod_elf_new(file->memory, file->size);
427 	return file->elf;
428 }
429 
kmod_file_open(const struct kmod_ctx * ctx,const char * filename)430 struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx,
431 						const char *filename)
432 {
433 	struct kmod_file *file = calloc(1, sizeof(struct kmod_file));
434 	const struct comp_type *itr;
435 	size_t magic_size_max = 0;
436 	int err = 0;
437 
438 	if (file == NULL)
439 		return NULL;
440 
441 	file->fd = open(filename, O_RDONLY|O_CLOEXEC);
442 	if (file->fd < 0) {
443 		err = -errno;
444 		goto error;
445 	}
446 
447 	for (itr = comp_types; itr->ops.load != NULL; itr++) {
448 		if (magic_size_max < itr->magic_size)
449 			magic_size_max = itr->magic_size;
450 	}
451 
452 	if (magic_size_max > 0) {
453 		char *buf = alloca(magic_size_max + 1);
454 		ssize_t sz;
455 
456 		if (buf == NULL) {
457 			err = -errno;
458 			goto error;
459 		}
460 		sz = read_str_safe(file->fd, buf, magic_size_max + 1);
461 		lseek(file->fd, 0, SEEK_SET);
462 		if (sz != (ssize_t)magic_size_max) {
463 			if (sz < 0)
464 				err = sz;
465 			else
466 				err = -EINVAL;
467 			goto error;
468 		}
469 
470 		for (itr = comp_types; itr->ops.load != NULL; itr++) {
471 			if (memcmp(buf, itr->magic_bytes, itr->magic_size) == 0) {
472 				file->ops = &itr->ops;
473 				file->compression = itr->compression;
474 				break;
475 			}
476 		}
477 	}
478 
479 	if (file->ops == NULL) {
480 		file->ops = &reg_ops;
481 		file->compression = KMOD_FILE_COMPRESSION_NONE;
482 	}
483 
484 	file->ctx = ctx;
485 
486 error:
487 	if (err < 0) {
488 		if (file->fd >= 0)
489 			close(file->fd);
490 		free(file);
491 		errno = -err;
492 		return NULL;
493 	}
494 
495 	return file;
496 }
497 
498 /*
499  *  Callers should just check file->memory got updated
500  */
kmod_file_load_contents(struct kmod_file * file)501 void kmod_file_load_contents(struct kmod_file *file)
502 {
503 	if (file->memory)
504 		return;
505 
506 	/*  The load functions already log possible errors. */
507 	file->ops->load(file);
508 }
509 
kmod_file_get_contents(const struct kmod_file * file)510 void *kmod_file_get_contents(const struct kmod_file *file)
511 {
512 	return file->memory;
513 }
514 
kmod_file_get_size(const struct kmod_file * file)515 off_t kmod_file_get_size(const struct kmod_file *file)
516 {
517 	return file->size;
518 }
519 
kmod_file_get_compression(const struct kmod_file * file)520 enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file)
521 {
522 	return file->compression;
523 }
524 
kmod_file_get_fd(const struct kmod_file * file)525 int kmod_file_get_fd(const struct kmod_file *file)
526 {
527 	return file->fd;
528 }
529 
kmod_file_unref(struct kmod_file * file)530 void kmod_file_unref(struct kmod_file *file)
531 {
532 	if (file->elf)
533 		kmod_elf_unref(file->elf);
534 
535 	if (file->memory)
536 		file->ops->unload(file);
537 
538 	if (file->fd >= 0)
539 		close(file->fd);
540 	free(file);
541 }
542