blob: ea3a5d7bec9dbd8dfed62cce7c071cf3b462881b [file] [log] [blame]
From f299d5bce82e7bd9941aa13337a3e22eaf4fac51 Mon Sep 17 00:00:00 2001
From: Dmitry Torokhov <dtor@chromium.org>
Date: Sun, 14 Nov 2021 14:25:27 -0800
Subject: [PATCH] kmod: add support for passing compressed modules directly
into kernel
Kernel's load pinning facilities interfere with loading compressed
modules, because if decompression happens in userspace and module is
instantiated via init_module() call there is no way for kernel to
ascertain that module data is coming from a trusted source. To solve
this issue module decompression support was added to the kernel.
Let's use the new kernel facility, and if kernel indicates (via
/sys/module/compression) that that it can handle certain compression
scheme, then use finit_module() and pass a file descriptor to the
compressed module instead of init_module() with decompressed data.
---
This is a backport to kmod-25 branch. The original version can be
found at:
https://lore.kernel.org/linux-modules/YbLo8wjBWDxwICf1@google.com/
libkmod/libkmod-file.c | 34 ++++++++++++++++++---------------
libkmod/libkmod-internal.h | 8 +++++++-
libkmod/libkmod-module.c | 39 +++++++++++++++++++++++++++++++++++++-
shared/missing.h | 4 ++++
4 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/libkmod/libkmod-file.c b/libkmod/libkmod-file.c
index 5eeba6a..7840c18 100644
--- a/libkmod/libkmod-file.c
+++ b/libkmod/libkmod-file.c
@@ -45,14 +45,11 @@ struct file_ops {
};
struct kmod_file {
-#ifdef ENABLE_XZ
- bool xz_used;
-#endif
+ enum kmod_compression compression;
#ifdef ENABLE_ZLIB
gzFile gzf;
#endif
int fd;
- bool direct;
off_t size;
void *memory;
const struct file_ops *ops;
@@ -131,7 +128,7 @@ static int xz_uncompress(lzma_stream *strm, struct kmod_file *file)
goto out;
}
}
- file->xz_used = true;
+ file->compression = KMOD_COMPRESSION_XZ;
file->memory = p;
file->size = total;
return 0;
@@ -161,7 +158,7 @@ static int load_xz(struct kmod_file *file)
static void unload_xz(struct kmod_file *file)
{
- if (!file->xz_used)
+ if (file->compression != KMOD_COMPRESSION_XZ)
return;
free(file->memory);
}
@@ -173,15 +170,20 @@ static const char magic_xz[] = {0xfd, '7', 'z', 'X', 'Z', 0};
#define READ_STEP (4 * 1024 * 1024)
static int load_zlib(struct kmod_file *file)
{
+ int gzf_fd;
int err = 0;
off_t did = 0, total = 0;
_cleanup_free_ unsigned char *p = NULL;
errno = 0;
- file->gzf = gzdopen(file->fd, "rb");
+
+ gzf_fd = dup(file->fd);
+ if (gzf_fd < 0)
+ return -errno;
+
+ file->gzf = gzdopen(gzf_fd, "rb");
if (file->gzf == NULL)
return -errno;
- file->fd = -1; /* now owned by gzf due gzdopen() */
for (;;) {
int r;
@@ -212,22 +214,24 @@ static int load_zlib(struct kmod_file *file)
did += r;
}
+ file->compression = KMOD_COMPRESSION_GZIP;
file->memory = p;
file->size = did;
p = NULL;
return 0;
error:
- gzclose(file->gzf);
+ if (file->gzf)
+ gzclose(file->gzf);
return err;
}
static void unload_zlib(struct kmod_file *file)
{
- if (file->gzf == NULL)
+ if (file->compression != KMOD_COMPRESSION_GZIP)
return;
free(file->memory);
- gzclose(file->gzf); /* closes file->fd */
+ gzclose(file->gzf);
}
static const char magic_zlib[] = {0x1f, 0x8b};
@@ -259,7 +263,8 @@ static int load_reg(struct kmod_file *file)
file->fd, 0);
if (file->memory == MAP_FAILED)
return -errno;
- file->direct = true;
+
+ file->compression = KMOD_COMPRESSION_NONE;
return 0;
}
@@ -303,7 +308,6 @@ struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx,
magic_size_max = itr->magic_size;
}
- file->direct = false;
if (magic_size_max > 0) {
char *buf = alloca(magic_size_max + 1);
ssize_t sz;
@@ -357,9 +361,9 @@ off_t kmod_file_get_size(const struct kmod_file *file)
return file->size;
}
-bool kmod_file_get_direct(const struct kmod_file *file)
+enum kmod_compression kmod_file_get_compression(const struct kmod_file *file)
{
- return file->direct;
+ return file->compression;
}
int kmod_file_get_fd(const struct kmod_file *file)
diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
index 346579c..93eefac 100644
--- a/libkmod/libkmod-internal.h
+++ b/libkmod/libkmod-internal.h
@@ -46,6 +46,12 @@ static _always_inline_ _printf_format_(2, 3) void
# endif
#endif
+enum kmod_compression {
+ KMOD_COMPRESSION_NONE,
+ KMOD_COMPRESSION_GZIP,
+ KMOD_COMPRESSION_XZ,
+};
+
void kmod_log(const struct kmod_ctx *ctx,
int priority, const char *file, int line, const char *fn,
const char *format, ...) __attribute__((format(printf, 6, 7))) __attribute__((nonnull(1, 3, 5)));
@@ -151,7 +157,7 @@ struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, const char *filenam
struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) __attribute__((nonnull(1)));
void *kmod_file_get_contents(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1)));
off_t kmod_file_get_size(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1)));
-bool kmod_file_get_direct(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1)));
+enum kmod_compression kmod_file_get_compression(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1)));
int kmod_file_get_fd(const struct kmod_file *file) _must_check_ __attribute__((nonnull(1)));
void kmod_file_unref(struct kmod_file *file) __attribute__((nonnull(1)));
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 0a3ef11..ff8d438 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -796,6 +796,37 @@ KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
return err;
}
+static enum kmod_compression kmod_get_supported_compression(struct kmod_module *mod)
+{
+ static const char path[] = "/sys/module/compression";
+ char buf[16];
+ int err;
+ int fd;
+
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ DBG(mod->ctx, "could not open '%s': %s\n",
+ path, strerror(errno));
+ goto fail;
+ }
+
+ err = read_str_safe(fd, buf, sizeof(buf));
+ close(fd);
+ if (err < 0) {
+ ERR(mod->ctx, "could not read from '%s': %s\n",
+ path, strerror(-err));
+ goto fail;
+ }
+
+ if (streq(buf, "gzip\n"))
+ return KMOD_COMPRESSION_GZIP;
+ else if (streq(buf, "xz\n"))
+ return KMOD_COMPRESSION_XZ;
+
+fail:
+ return KMOD_COMPRESSION_NONE;
+}
+
extern long init_module(const void *mem, unsigned long len, const char *args);
/**
@@ -823,6 +854,7 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
struct kmod_elf *elf;
const char *path;
const char *args = options ? options : "";
+ enum kmod_compression compression;
if (mod == NULL)
return -ENOENT;
@@ -841,7 +873,9 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
}
}
- if (kmod_file_get_direct(mod->file)) {
+ compression = kmod_file_get_compression(mod->file);
+ if (compression == KMOD_COMPRESSION_NONE ||
+ compression == kmod_get_supported_compression(mod)) {
unsigned int kernel_flags = 0;
if (flags & KMOD_INSERT_FORCE_VERMAGIC)
@@ -849,6 +883,9 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
if (flags & KMOD_INSERT_FORCE_MODVERSION)
kernel_flags |= MODULE_INIT_IGNORE_MODVERSIONS;
+ if (compression != KMOD_COMPRESSION_NONE)
+ kernel_flags |= MODULE_INIT_COMPRESSED_FILE;
+
err = finit_module(kmod_file_get_fd(mod->file), args, kernel_flags);
if (err == 0 || errno != ENOSYS)
goto init_finished;
diff --git a/shared/missing.h b/shared/missing.h
index 4c0d136..2629444 100644
--- a/shared/missing.h
+++ b/shared/missing.h
@@ -15,6 +15,10 @@
# define MODULE_INIT_IGNORE_VERMAGIC 2
#endif
+#ifndef MODULE_INIT_COMPRESSED_FILE
+# define MODULE_INIT_COMPRESSED_FILE 4
+#endif
+
#ifndef __NR_finit_module
# define __NR_finit_module -1
#endif
--
2.34.1.173.g76aa8bc2d0-goog