Commit 438b8050 authored by Ard Biesheuvel's avatar Ard Biesheuvel Committed by Kees Cook

pstore: Replace crypto API compression with zlib_deflate library calls

Pstore supports compression using a variety of algorithms exposed by the
crypto API. This uses the deprecated comp (as opposed to scomp/acomp)
API, and so we should stop using that, and either move to the new API,
or switch to a different approach entirely.

Given that we only compress ASCII text in pstore, and considering that
this happens when the system is likely to be in an unstable state, the
flexibility that the complex crypto API provides does not outweigh its
impact on the risk that we might encounter additional problems when
trying to commit the kernel log contents to the pstore backend.

So let's switch [back] to the zlib deflate library API, and remove all
the complexity that really has no place in a low-level diagnostic
facility. Note that, while more modern compression algorithms have been
added to the kernel in recent years, the code size of zlib deflate is
substantially smaller than, e.g., zstd, while its performance in terms
of compression ratio is comparable for ASCII text, and speed is deemed
irrelevant in this context.

Note that this means that compressed pstore records may no longer be
accessible after a kernel upgrade, but this has never been part of the
contract. (The choice of compression algorithm is not stored in the
pstore records either)

Tested-by: "Guilherme G. Piccoli" <gpiccoli@igalia.com> # Steam Deck
Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
Reviewed-by: default avatarEric Biggers <ebiggers@google.com>
Link: https://lore.kernel.org/r/20230712162332.2670437-3-ardb@kernel.orgSigned-off-by: default avatarKees Cook <keescook@chromium.org>
parent 1756ddea
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
config PSTORE config PSTORE
tristate "Persistent store support" tristate "Persistent store support"
select CRYPTO if PSTORE_COMPRESS
default n default n
help help
This option enables generic access to platform level This option enables generic access to platform level
...@@ -22,99 +21,18 @@ config PSTORE_DEFAULT_KMSG_BYTES ...@@ -22,99 +21,18 @@ config PSTORE_DEFAULT_KMSG_BYTES
Defines default size of pstore kernel log storage. Defines default size of pstore kernel log storage.
Can be enlarged if needed, not recommended to shrink it. Can be enlarged if needed, not recommended to shrink it.
config PSTORE_DEFLATE_COMPRESS
tristate "DEFLATE (ZLIB) compression"
default y
depends on PSTORE
select CRYPTO_DEFLATE
help
This option enables DEFLATE (also known as ZLIB) compression
algorithm support.
config PSTORE_LZO_COMPRESS
tristate "LZO compression"
depends on PSTORE
select CRYPTO_LZO
help
This option enables LZO compression algorithm support.
config PSTORE_LZ4_COMPRESS
tristate "LZ4 compression"
depends on PSTORE
select CRYPTO_LZ4
help
This option enables LZ4 compression algorithm support.
config PSTORE_LZ4HC_COMPRESS
tristate "LZ4HC compression"
depends on PSTORE
select CRYPTO_LZ4HC
help
This option enables LZ4HC (high compression) mode algorithm.
config PSTORE_842_COMPRESS
bool "842 compression"
depends on PSTORE
select CRYPTO_842
help
This option enables 842 compression algorithm support.
config PSTORE_ZSTD_COMPRESS
bool "zstd compression"
depends on PSTORE
select CRYPTO_ZSTD
help
This option enables zstd compression algorithm support.
config PSTORE_COMPRESS config PSTORE_COMPRESS
def_bool y bool "Pstore compression (deflate)"
depends on PSTORE depends on PSTORE
depends on PSTORE_DEFLATE_COMPRESS || PSTORE_LZO_COMPRESS || \ select ZLIB_INFLATE
PSTORE_LZ4_COMPRESS || PSTORE_LZ4HC_COMPRESS || \ select ZLIB_DEFLATE
PSTORE_842_COMPRESS || PSTORE_ZSTD_COMPRESS default y
choice
prompt "Default pstore compression algorithm"
depends on PSTORE_COMPRESS
help help
This option chooses the default active compression algorithm. Whether pstore records should be compressed before being written to
This change be changed at boot with "pstore.compress=..." on the backing store. This is implemented using the zlib 'deflate'
the kernel command line. algorithm, using the library implementation instead of using the full
blown crypto API. This reduces the risk of secondary oopses or other
Currently, pstore has support for 6 compression algorithms: problems while pstore is recording panic metadata.
deflate, lzo, lz4, lz4hc, 842 and zstd.
The default compression algorithm is deflate.
config PSTORE_DEFLATE_COMPRESS_DEFAULT
bool "deflate" if PSTORE_DEFLATE_COMPRESS
config PSTORE_LZO_COMPRESS_DEFAULT
bool "lzo" if PSTORE_LZO_COMPRESS
config PSTORE_LZ4_COMPRESS_DEFAULT
bool "lz4" if PSTORE_LZ4_COMPRESS
config PSTORE_LZ4HC_COMPRESS_DEFAULT
bool "lz4hc" if PSTORE_LZ4HC_COMPRESS
config PSTORE_842_COMPRESS_DEFAULT
bool "842" if PSTORE_842_COMPRESS
config PSTORE_ZSTD_COMPRESS_DEFAULT
bool "zstd" if PSTORE_ZSTD_COMPRESS
endchoice
config PSTORE_COMPRESS_DEFAULT
string
depends on PSTORE_COMPRESS
default "deflate" if PSTORE_DEFLATE_COMPRESS_DEFAULT
default "lzo" if PSTORE_LZO_COMPRESS_DEFAULT
default "lz4" if PSTORE_LZ4_COMPRESS_DEFAULT
default "lz4hc" if PSTORE_LZ4HC_COMPRESS_DEFAULT
default "842" if PSTORE_842_COMPRESS_DEFAULT
default "zstd" if PSTORE_ZSTD_COMPRESS_DEFAULT
config PSTORE_CONSOLE config PSTORE_CONSOLE
bool "Log kernel console messages" bool "Log kernel console messages"
......
...@@ -16,13 +16,14 @@ ...@@ -16,13 +16,14 @@
#include <linux/console.h> #include <linux/console.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pstore.h> #include <linux/pstore.h>
#include <linux/crypto.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/zlib.h>
#include "internal.h" #include "internal.h"
...@@ -71,12 +72,21 @@ static char *backend; ...@@ -71,12 +72,21 @@ static char *backend;
module_param(backend, charp, 0444); module_param(backend, charp, 0444);
MODULE_PARM_DESC(backend, "specific backend to use"); MODULE_PARM_DESC(backend, "specific backend to use");
static char *compress = /*
#ifdef CONFIG_PSTORE_COMPRESS_DEFAULT * pstore no longer implements compression via the crypto API, and only
CONFIG_PSTORE_COMPRESS_DEFAULT; * supports zlib deflate compression implemented using the zlib library
#else * interface. This removes additional complexity which is hard to justify for a
NULL; * diagnostic facility that has to operate in conditions where the system may
#endif * have become unstable. Zlib deflate is comparatively small in terms of code
* size, and compresses ASCII text comparatively well. In terms of compression
* speed, deflate is not the best performer but for recording the log output on
* a kernel panic, this is not considered critical.
*
* The only remaining arguments supported by the compress= module parameter are
* 'deflate' and 'none'. To retain compatibility with existing installations,
* all other values are logged and replaced with 'deflate'.
*/
static char *compress = "deflate";
module_param(compress, charp, 0444); module_param(compress, charp, 0444);
MODULE_PARM_DESC(compress, "compression to use"); MODULE_PARM_DESC(compress, "compression to use");
...@@ -85,8 +95,7 @@ unsigned long kmsg_bytes = CONFIG_PSTORE_DEFAULT_KMSG_BYTES; ...@@ -85,8 +95,7 @@ unsigned long kmsg_bytes = CONFIG_PSTORE_DEFAULT_KMSG_BYTES;
module_param(kmsg_bytes, ulong, 0444); module_param(kmsg_bytes, ulong, 0444);
MODULE_PARM_DESC(kmsg_bytes, "amount of kernel log to snapshot (in bytes)"); MODULE_PARM_DESC(kmsg_bytes, "amount of kernel log to snapshot (in bytes)");
/* Compression parameters */ static void *compress_workspace;
static struct crypto_comp *tfm;
static char *big_oops_buf; static char *big_oops_buf;
...@@ -156,36 +165,49 @@ static bool pstore_cannot_block_path(enum kmsg_dump_reason reason) ...@@ -156,36 +165,49 @@ static bool pstore_cannot_block_path(enum kmsg_dump_reason reason)
static int pstore_compress(const void *in, void *out, static int pstore_compress(const void *in, void *out,
unsigned int inlen, unsigned int outlen) unsigned int inlen, unsigned int outlen)
{ {
struct z_stream_s zstream = {
.next_in = in,
.avail_in = inlen,
.next_out = out,
.avail_out = outlen,
.workspace = compress_workspace,
};
int ret; int ret;
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS)) if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS))
return -EINVAL; return -EINVAL;
ret = crypto_comp_compress(tfm, in, inlen, out, &outlen); ret = zlib_deflateInit2(&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
if (ret) { -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
pr_err("crypto_comp_compress failed, ret = %d!\n", ret); if (ret != Z_OK)
return ret; return -EINVAL;
}
return outlen; ret = zlib_deflate(&zstream, Z_FINISH);
if (ret != Z_STREAM_END)
return -EINVAL;
ret = zlib_deflateEnd(&zstream);
if (ret != Z_OK)
pr_warn_once("zlib_deflateEnd() failed: %d\n", ret);
return zstream.total_out;
} }
static void allocate_buf_for_compression(void) static void allocate_buf_for_compression(void)
{ {
struct crypto_comp *ctx;
char *buf; char *buf;
/* Skip if not built-in or compression backend not selected yet. */ /* Skip if not built-in or compression disabled. */
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !compress) if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !compress ||
return; !strcmp(compress, "none")) {
compress = NULL;
/* Skip if no pstore backend yet or compression init already done. */
if (!psinfo || tfm)
return; return;
}
if (!crypto_has_comp(compress, 0, 0)) { if (strcmp(compress, "deflate")) {
pr_err("Unknown compression: %s\n", compress); pr_err("Unsupported compression '%s', falling back to deflate\n",
return; compress);
compress = "deflate";
} }
/* /*
...@@ -200,16 +222,15 @@ static void allocate_buf_for_compression(void) ...@@ -200,16 +222,15 @@ static void allocate_buf_for_compression(void)
return; return;
} }
ctx = crypto_alloc_comp(compress, 0, 0); compress_workspace =
if (IS_ERR_OR_NULL(ctx)) { vmalloc(zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL));
if (!compress_workspace) {
pr_err("Failed to allocate zlib deflate workspace\n");
kfree(buf); kfree(buf);
pr_err("crypto_alloc_comp('%s') failed: %ld\n", compress,
PTR_ERR(ctx));
return; return;
} }
/* A non-NULL big_oops_buf indicates compression is available. */ /* A non-NULL big_oops_buf indicates compression is available. */
tfm = ctx;
big_oops_buf = buf; big_oops_buf = buf;
pr_info("Using crash dump compression: %s\n", compress); pr_info("Using crash dump compression: %s\n", compress);
...@@ -217,10 +238,11 @@ static void allocate_buf_for_compression(void) ...@@ -217,10 +238,11 @@ static void allocate_buf_for_compression(void)
static void free_buf_for_compression(void) static void free_buf_for_compression(void)
{ {
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && tfm) { if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress_workspace) {
crypto_free_comp(tfm); vfree(compress_workspace);
tfm = NULL; compress_workspace = NULL;
} }
kfree(big_oops_buf); kfree(big_oops_buf);
big_oops_buf = NULL; big_oops_buf = NULL;
} }
...@@ -531,7 +553,8 @@ void pstore_unregister(struct pstore_info *psi) ...@@ -531,7 +553,8 @@ void pstore_unregister(struct pstore_info *psi)
} }
EXPORT_SYMBOL_GPL(pstore_unregister); EXPORT_SYMBOL_GPL(pstore_unregister);
static void decompress_record(struct pstore_record *record) static void decompress_record(struct pstore_record *record,
struct z_stream_s *zstream)
{ {
int ret; int ret;
int unzipped_len; int unzipped_len;
...@@ -547,26 +570,37 @@ static void decompress_record(struct pstore_record *record) ...@@ -547,26 +570,37 @@ static void decompress_record(struct pstore_record *record)
} }
/* Missing compression buffer means compression was not initialized. */ /* Missing compression buffer means compression was not initialized. */
if (!big_oops_buf) { if (!zstream->workspace) {
pr_warn("no decompression method initialized!\n"); pr_warn("no decompression method initialized!\n");
return; return;
} }
ret = zlib_inflateReset(zstream);
if (ret != Z_OK) {
pr_err("zlib_inflateReset() failed, ret = %d!\n", ret);
return;
}
/* Allocate enough space to hold max decompression and ECC. */ /* Allocate enough space to hold max decompression and ECC. */
workspace = kmalloc(psinfo->bufsize + record->ecc_notice_size, workspace = kmalloc(psinfo->bufsize + record->ecc_notice_size,
GFP_KERNEL); GFP_KERNEL);
if (!workspace) if (!workspace)
return; return;
/* After decompression "unzipped_len" is almost certainly smaller. */ zstream->next_in = record->buf;
ret = crypto_comp_decompress(tfm, record->buf, record->size, zstream->avail_in = record->size;
workspace, &unzipped_len); zstream->next_out = workspace;
if (ret) { zstream->avail_out = psinfo->bufsize;
pr_err("crypto_comp_decompress failed, ret = %d!\n", ret);
ret = zlib_inflate(zstream, Z_FINISH);
if (ret != Z_STREAM_END) {
pr_err("zlib_inflate() failed, ret = %d!\n", ret);
kfree(workspace); kfree(workspace);
return; return;
} }
unzipped_len = zstream->total_out;
/* Append ECC notice to decompressed buffer. */ /* Append ECC notice to decompressed buffer. */
memcpy(workspace + unzipped_len, record->buf + record->size, memcpy(workspace + unzipped_len, record->buf + record->size,
record->ecc_notice_size); record->ecc_notice_size);
...@@ -596,10 +630,17 @@ void pstore_get_backend_records(struct pstore_info *psi, ...@@ -596,10 +630,17 @@ void pstore_get_backend_records(struct pstore_info *psi,
{ {
int failed = 0; int failed = 0;
unsigned int stop_loop = 65536; unsigned int stop_loop = 65536;
struct z_stream_s zstream = {};
if (!psi || !root) if (!psi || !root)
return; return;
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress) {
zstream.workspace = kvmalloc(zlib_inflate_workspacesize(),
GFP_KERNEL);
zlib_inflateInit2(&zstream, -DEF_WBITS);
}
mutex_lock(&psi->read_mutex); mutex_lock(&psi->read_mutex);
if (psi->open && psi->open(psi)) if (psi->open && psi->open(psi))
goto out; goto out;
...@@ -628,7 +669,7 @@ void pstore_get_backend_records(struct pstore_info *psi, ...@@ -628,7 +669,7 @@ void pstore_get_backend_records(struct pstore_info *psi,
break; break;
} }
decompress_record(record); decompress_record(record, &zstream);
rc = pstore_mkfile(root, record); rc = pstore_mkfile(root, record);
if (rc) { if (rc) {
/* pstore_mkfile() did not take record, so free it. */ /* pstore_mkfile() did not take record, so free it. */
...@@ -644,6 +685,12 @@ void pstore_get_backend_records(struct pstore_info *psi, ...@@ -644,6 +685,12 @@ void pstore_get_backend_records(struct pstore_info *psi,
out: out:
mutex_unlock(&psi->read_mutex); mutex_unlock(&psi->read_mutex);
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress) {
if (zlib_inflateEnd(&zstream) != Z_OK)
pr_warn("zlib_inflateEnd() failed\n");
kvfree(zstream.workspace);
}
if (failed) if (failed)
pr_warn("failed to create %d record(s) from '%s'\n", pr_warn("failed to create %d record(s) from '%s'\n",
failed, psi->name); failed, psi->name);
...@@ -671,13 +718,6 @@ static int __init pstore_init(void) ...@@ -671,13 +718,6 @@ static int __init pstore_init(void)
{ {
int ret; int ret;
/*
* Check if any pstore backends registered earlier but did not
* initialize compression because crypto was not ready. If so,
* initialize compression now.
*/
allocate_buf_for_compression();
ret = pstore_init_fs(); ret = pstore_init_fs();
if (ret) if (ret)
free_buf_for_compression(); free_buf_for_compression();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment