Commit 104fd0b5 authored by Yuxiao Zhang's avatar Yuxiao Zhang Committed by Kees Cook

pstore: Support record sizes larger than kmalloc() limit

Currently pstore record buffers are allocated using kmalloc() which has
a maximum size based on page size. If a large "pmsg-size" module
parameter is specified, pmsg will fail to copy the contents since
memdup_user() is limited to kmalloc() allocation sizes.

Since we don't need physically contiguous memory for any of the pstore
record buffers, use kvzalloc() to avoid such limitations in the core of
pstore and in the ram backend, and explicitly read from userspace using
vmemdup_user(). This also means that any other backends that want to
(or do already) support larger record sizes will Just Work now.
Signed-off-by: default avatarYuxiao Zhang <yuxiaozhang@google.com>
Link: https://lore.kernel.org/r/20230627202540.881909-2-yuxiaozhang@google.comCo-developed-by: default avatarKees Cook <keescook@chromium.org>
Signed-off-by: default avatarKees Cook <keescook@chromium.org>
parent fe8c3623
...@@ -54,7 +54,7 @@ static void free_pstore_private(struct pstore_private *private) ...@@ -54,7 +54,7 @@ static void free_pstore_private(struct pstore_private *private)
if (!private) if (!private)
return; return;
if (private->record) { if (private->record) {
kfree(private->record->buf); kvfree(private->record->buf);
kfree(private->record->priv); kfree(private->record->priv);
kfree(private->record); kfree(private->record);
} }
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/kmsg_dump.h> #include <linux/kmsg_dump.h>
#include <linux/console.h> #include <linux/console.h>
#include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pstore.h> #include <linux/pstore.h>
#include <linux/string.h> #include <linux/string.h>
...@@ -215,7 +216,7 @@ static void allocate_buf_for_compression(void) ...@@ -215,7 +216,7 @@ static void allocate_buf_for_compression(void)
* uncompressed record size, since any record that would be expanded by * uncompressed record size, since any record that would be expanded by
* compression is just stored uncompressed. * compression is just stored uncompressed.
*/ */
buf = kmalloc(psinfo->bufsize, GFP_KERNEL); buf = kvzalloc(psinfo->bufsize, GFP_KERNEL);
if (!buf) { if (!buf) {
pr_err("Failed %zu byte compression buffer allocation for: %s\n", pr_err("Failed %zu byte compression buffer allocation for: %s\n",
psinfo->bufsize, compress); psinfo->bufsize, compress);
...@@ -226,7 +227,7 @@ static void allocate_buf_for_compression(void) ...@@ -226,7 +227,7 @@ static void allocate_buf_for_compression(void)
vmalloc(zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL)); vmalloc(zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL));
if (!compress_workspace) { if (!compress_workspace) {
pr_err("Failed to allocate zlib deflate workspace\n"); pr_err("Failed to allocate zlib deflate workspace\n");
kfree(buf); kvfree(buf);
return; return;
} }
...@@ -243,7 +244,7 @@ static void free_buf_for_compression(void) ...@@ -243,7 +244,7 @@ static void free_buf_for_compression(void)
compress_workspace = NULL; compress_workspace = NULL;
} }
kfree(big_oops_buf); kvfree(big_oops_buf);
big_oops_buf = NULL; big_oops_buf = NULL;
} }
...@@ -421,7 +422,7 @@ static int pstore_write_user_compat(struct pstore_record *record, ...@@ -421,7 +422,7 @@ static int pstore_write_user_compat(struct pstore_record *record,
if (record->buf) if (record->buf)
return -EINVAL; return -EINVAL;
record->buf = memdup_user(buf, record->size); record->buf = vmemdup_user(buf, record->size);
if (IS_ERR(record->buf)) { if (IS_ERR(record->buf)) {
ret = PTR_ERR(record->buf); ret = PTR_ERR(record->buf);
goto out; goto out;
...@@ -429,7 +430,7 @@ static int pstore_write_user_compat(struct pstore_record *record, ...@@ -429,7 +430,7 @@ static int pstore_write_user_compat(struct pstore_record *record,
ret = record->psi->write(record); ret = record->psi->write(record);
kfree(record->buf); kvfree(record->buf);
out: out:
record->buf = NULL; record->buf = NULL;
...@@ -582,8 +583,8 @@ static void decompress_record(struct pstore_record *record, ...@@ -582,8 +583,8 @@ static void decompress_record(struct pstore_record *record,
} }
/* 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 = kvzalloc(psinfo->bufsize + record->ecc_notice_size,
GFP_KERNEL); GFP_KERNEL);
if (!workspace) if (!workspace)
return; return;
...@@ -595,7 +596,7 @@ static void decompress_record(struct pstore_record *record, ...@@ -595,7 +596,7 @@ static void decompress_record(struct pstore_record *record,
ret = zlib_inflate(zstream, Z_FINISH); ret = zlib_inflate(zstream, Z_FINISH);
if (ret != Z_STREAM_END) { if (ret != Z_STREAM_END) {
pr_err("zlib_inflate() failed, ret = %d!\n", ret); pr_err("zlib_inflate() failed, ret = %d!\n", ret);
kfree(workspace); kvfree(workspace);
return; return;
} }
...@@ -606,14 +607,14 @@ static void decompress_record(struct pstore_record *record, ...@@ -606,14 +607,14 @@ static void decompress_record(struct pstore_record *record,
record->ecc_notice_size); record->ecc_notice_size);
/* Copy decompressed contents into an minimum-sized allocation. */ /* Copy decompressed contents into an minimum-sized allocation. */
unzipped = kmemdup(workspace, unzipped_len + record->ecc_notice_size, unzipped = kvmemdup(workspace, unzipped_len + record->ecc_notice_size,
GFP_KERNEL); GFP_KERNEL);
kfree(workspace); kvfree(workspace);
if (!unzipped) if (!unzipped)
return; return;
/* Swap out compressed contents with decompressed contents. */ /* Swap out compressed contents with decompressed contents. */
kfree(record->buf); kvfree(record->buf);
record->buf = unzipped; record->buf = unzipped;
record->size = unzipped_len; record->size = unzipped_len;
record->compressed = false; record->compressed = false;
...@@ -673,7 +674,7 @@ void pstore_get_backend_records(struct pstore_info *psi, ...@@ -673,7 +674,7 @@ void pstore_get_backend_records(struct pstore_info *psi,
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. */
kfree(record->buf); kvfree(record->buf);
kfree(record->priv); kfree(record->priv);
kfree(record); kfree(record);
if (rc != -EEXIST || !quiet) if (rc != -EEXIST || !quiet)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/mm.h>
#include "internal.h" #include "internal.h"
#include "ram_internal.h" #include "ram_internal.h"
...@@ -268,7 +269,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record) ...@@ -268,7 +269,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
/* ECC correction notice */ /* ECC correction notice */
record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0); record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0);
record->buf = kmalloc(size + record->ecc_notice_size + 1, GFP_KERNEL); record->buf = kvzalloc(size + record->ecc_notice_size + 1, GFP_KERNEL);
if (record->buf == NULL) { if (record->buf == NULL) {
size = -ENOMEM; size = -ENOMEM;
goto out; goto out;
...@@ -282,7 +283,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record) ...@@ -282,7 +283,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
out: out:
if (free_prz) { if (free_prz) {
kfree(prz->old_log); kvfree(prz->old_log);
kfree(prz); kfree(prz);
} }
...@@ -833,7 +834,7 @@ static int ramoops_probe(struct platform_device *pdev) ...@@ -833,7 +834,7 @@ static int ramoops_probe(struct platform_device *pdev)
*/ */
if (cxt->pstore.flags & PSTORE_FLAGS_DMESG) { if (cxt->pstore.flags & PSTORE_FLAGS_DMESG) {
cxt->pstore.bufsize = cxt->dprzs[0]->buffer_size; cxt->pstore.bufsize = cxt->dprzs[0]->buffer_size;
cxt->pstore.buf = kzalloc(cxt->pstore.bufsize, GFP_KERNEL); cxt->pstore.buf = kvzalloc(cxt->pstore.bufsize, GFP_KERNEL);
if (!cxt->pstore.buf) { if (!cxt->pstore.buf) {
pr_err("cannot allocate pstore crash dump buffer\n"); pr_err("cannot allocate pstore crash dump buffer\n");
err = -ENOMEM; err = -ENOMEM;
...@@ -866,7 +867,7 @@ static int ramoops_probe(struct platform_device *pdev) ...@@ -866,7 +867,7 @@ static int ramoops_probe(struct platform_device *pdev)
return 0; return 0;
fail_buf: fail_buf:
kfree(cxt->pstore.buf); kvfree(cxt->pstore.buf);
fail_clear: fail_clear:
cxt->pstore.bufsize = 0; cxt->pstore.bufsize = 0;
fail_init: fail_init:
...@@ -881,7 +882,7 @@ static void ramoops_remove(struct platform_device *pdev) ...@@ -881,7 +882,7 @@ static void ramoops_remove(struct platform_device *pdev)
pstore_unregister(&cxt->pstore); pstore_unregister(&cxt->pstore);
kfree(cxt->pstore.buf); kvfree(cxt->pstore.buf);
cxt->pstore.bufsize = 0; cxt->pstore.bufsize = 0;
ramoops_free_przs(cxt); ramoops_free_przs(cxt);
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/page.h> #include <asm/page.h>
#include "ram_internal.h" #include "ram_internal.h"
...@@ -301,7 +302,7 @@ void persistent_ram_save_old(struct persistent_ram_zone *prz) ...@@ -301,7 +302,7 @@ void persistent_ram_save_old(struct persistent_ram_zone *prz)
if (!prz->old_log) { if (!prz->old_log) {
persistent_ram_ecc_old(prz); persistent_ram_ecc_old(prz);
prz->old_log = kmalloc(size, GFP_KERNEL); prz->old_log = kvzalloc(size, GFP_KERNEL);
} }
if (!prz->old_log) { if (!prz->old_log) {
pr_err("failed to allocate buffer\n"); pr_err("failed to allocate buffer\n");
...@@ -385,7 +386,7 @@ void *persistent_ram_old(struct persistent_ram_zone *prz) ...@@ -385,7 +386,7 @@ void *persistent_ram_old(struct persistent_ram_zone *prz)
void persistent_ram_free_old(struct persistent_ram_zone *prz) void persistent_ram_free_old(struct persistent_ram_zone *prz)
{ {
kfree(prz->old_log); kvfree(prz->old_log);
prz->old_log = NULL; prz->old_log = NULL;
prz->old_log_size = 0; prz->old_log_size = 0;
} }
......
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