From f6f8285132907757ef84ef8dae0a1244b8cde6ac Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Nov 2011 12:58:07 -0800 Subject: pstore: pass allocated memory region back to caller The buf_lock cannot be held while populating the inodes, so make the backend pass forward an allocated and filled buffer instead. This solves the following backtrace. The effect is that "buf" is only ever used to notify the backends that something was written to it, and shouldn't be used in the read path. To replace the buf_lock during the read path, isolate the open/read/close loop with a separate mutex to maintain serialized access to the backend. Note that is is up to the pstore backend to cope if the (*write)() path is called in the middle of the read path. [ 59.691019] BUG: sleeping function called from invalid context at .../mm/slub.c:847 [ 59.691019] in_atomic(): 0, irqs_disabled(): 1, pid: 1819, name: mount [ 59.691019] Pid: 1819, comm: mount Not tainted 3.0.8 #1 [ 59.691019] Call Trace: [ 59.691019] [<810252d5>] __might_sleep+0xc3/0xca [ 59.691019] [<810a26e6>] kmem_cache_alloc+0x32/0xf3 [ 59.691019] [<810b53ac>] ? __d_lookup_rcu+0x6f/0xf4 [ 59.691019] [<810b68b1>] alloc_inode+0x2a/0x64 [ 59.691019] [<810b6903>] new_inode+0x18/0x43 [ 59.691019] [<81142447>] pstore_get_inode.isra.1+0x11/0x98 [ 59.691019] [<81142623>] pstore_mkfile+0xae/0x26f [ 59.691019] [<810a2a66>] ? kmem_cache_free+0x19/0xb1 [ 59.691019] [<8116c821>] ? ida_get_new_above+0x140/0x158 [ 59.691019] [<811708ea>] ? __init_rwsem+0x1e/0x2c [ 59.691019] [<810b67e8>] ? inode_init_always+0x111/0x1b0 [ 59.691019] [<8102127e>] ? should_resched+0xd/0x27 [ 59.691019] [<8137977f>] ? _cond_resched+0xd/0x21 [ 59.691019] [<81142abf>] pstore_get_records+0x52/0xa7 [ 59.691019] [<8114254b>] pstore_fill_super+0x7d/0x91 [ 59.691019] [<810a7ff5>] mount_single+0x46/0x82 [ 59.691019] [<8114231a>] pstore_mount+0x15/0x17 [ 59.691019] [<811424ce>] ? pstore_get_inode.isra.1+0x98/0x98 [ 59.691019] [<810a8199>] mount_fs+0x5a/0x12d [ 59.691019] [<810b9174>] ? alloc_vfsmnt+0xa4/0x14a [ 59.691019] [<810b9474>] vfs_kern_mount+0x4f/0x7d [ 59.691019] [<810b9d7e>] do_kern_mount+0x34/0xb2 [ 59.691019] [<810bb15f>] do_mount+0x5fc/0x64a [ 59.691019] [<810912fb>] ? strndup_user+0x2e/0x3f [ 59.691019] [<810bb3cb>] sys_mount+0x66/0x99 [ 59.691019] [<8137b537>] sysenter_do_call+0x12/0x26 Signed-off-by: Kees Cook Signed-off-by: Tony Luck --- fs/pstore/platform.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'fs/pstore/platform.c') diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 2bd620f0d796..57bbf9078ac8 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -167,6 +167,7 @@ int pstore_register(struct pstore_info *psi) } psinfo = psi; + mutex_init(&psinfo->read_mutex); spin_unlock(&pstore_lock); if (owner && !try_module_get(owner)) { @@ -195,30 +196,32 @@ EXPORT_SYMBOL_GPL(pstore_register); void pstore_get_records(int quiet) { struct pstore_info *psi = psinfo; + char *buf = NULL; ssize_t size; u64 id; enum pstore_type_id type; struct timespec time; int failed = 0, rc; - unsigned long flags; if (!psi) return; - spin_lock_irqsave(&psinfo->buf_lock, flags); + mutex_lock(&psi->read_mutex); rc = psi->open(psi); if (rc) goto out; - while ((size = psi->read(&id, &type, &time, psi)) > 0) { - rc = pstore_mkfile(type, psi->name, id, psi->buf, (size_t)size, + while ((size = psi->read(&id, &type, &time, &buf, psi)) > 0) { + rc = pstore_mkfile(type, psi->name, id, buf, (size_t)size, time, psi); + kfree(buf); + buf = NULL; if (rc && (rc != -EEXIST || !quiet)) failed++; } psi->close(psi); out: - spin_unlock_irqrestore(&psinfo->buf_lock, flags); + mutex_unlock(&psi->read_mutex); if (failed) printk(KERN_WARNING "pstore: failed to load %d record(s) from '%s'\n", -- cgit v1.2.3 From 3d6d8d20ec4fd3b256632edb373a9c504724b8a9 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Nov 2011 13:13:29 -0800 Subject: pstore: pass reason to backend write callback This allows a backend to filter on the dmesg reason as well as the pstore reason. When ramoops is switched to pstore, this is needed since it has no interest in storing non-crash dmesg details. Drop pstore_write() as it has no users, and handling the "reason" here has no obviously correct value. Signed-off-by: Kees Cook Signed-off-by: Tony Luck --- drivers/acpi/apei/erst.c | 6 ++++-- drivers/firmware/efivars.c | 8 +++++--- fs/pstore/platform.c | 30 +----------------------------- include/linux/pstore.h | 12 +++++------- 4 files changed, 15 insertions(+), 41 deletions(-) (limited to 'fs/pstore/platform.c') diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index 631b9477b99c..6a9e3bad13f4 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c @@ -934,7 +934,8 @@ static int erst_close_pstore(struct pstore_info *psi); static ssize_t erst_reader(u64 *id, enum pstore_type_id *type, struct timespec *time, char **buf, struct pstore_info *psi); -static int erst_writer(enum pstore_type_id type, u64 *id, unsigned int part, +static int erst_writer(enum pstore_type_id type, enum kmsg_dump_reason reason, + u64 *id, unsigned int part, size_t size, struct pstore_info *psi); static int erst_clearer(enum pstore_type_id type, u64 id, struct pstore_info *psi); @@ -1053,7 +1054,8 @@ out: return (rc < 0) ? rc : (len - sizeof(*rcd)); } -static int erst_writer(enum pstore_type_id type, u64 *id, unsigned int part, +static int erst_writer(enum pstore_type_id type, enum kmsg_dump_reason reason, + u64 *id, unsigned int part, size_t size, struct pstore_info *psi) { struct cper_pstore_record *rcd = (struct cper_pstore_record *) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index a54a6b972ced..0a53a05a850d 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -495,7 +495,8 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, return 0; } -static int efi_pstore_write(enum pstore_type_id type, u64 *id, +static int efi_pstore_write(enum pstore_type_id type, + enum kmsg_dump_reason reason, u64 *id, unsigned int part, size_t size, struct pstore_info *psi) { char name[DUMP_NAME_LEN]; @@ -565,7 +566,7 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, static int efi_pstore_erase(enum pstore_type_id type, u64 id, struct pstore_info *psi) { - efi_pstore_write(type, &id, (unsigned int)id, 0, psi); + efi_pstore_write(type, 0, &id, (unsigned int)id, 0, psi); return 0; } @@ -586,7 +587,8 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, return -1; } -static int efi_pstore_write(enum pstore_type_id type, u64 *id, +static int efi_pstore_write(enum pstore_type_id type, + enum kmsg_dump_reason reason, u64 *id, unsigned int part, size_t size, struct pstore_info *psi) { return 0; diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 57bbf9078ac8..f146d89179bf 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -122,7 +122,7 @@ static void pstore_dump(struct kmsg_dumper *dumper, memcpy(dst, s1 + s1_start, l1_cpy); memcpy(dst + l1_cpy, s2 + s2_start, l2_cpy); - ret = psinfo->write(PSTORE_TYPE_DMESG, &id, part, + ret = psinfo->write(PSTORE_TYPE_DMESG, reason, &id, part, hsize + l1_cpy + l2_cpy, psinfo); if (ret == 0 && reason == KMSG_DUMP_OOPS && pstore_is_mounted()) pstore_new_entry = 1; @@ -243,33 +243,5 @@ static void pstore_timefunc(unsigned long dummy) mod_timer(&pstore_timer, jiffies + PSTORE_INTERVAL); } -/* - * Call platform driver to write a record to the - * persistent store. - */ -int pstore_write(enum pstore_type_id type, char *buf, size_t size) -{ - u64 id; - int ret; - unsigned long flags; - - if (!psinfo) - return -ENODEV; - - if (size > psinfo->bufsize) - return -EFBIG; - - spin_lock_irqsave(&psinfo->buf_lock, flags); - memcpy(psinfo->buf, buf, size); - ret = psinfo->write(type, &id, 0, size, psinfo); - if (ret == 0 && pstore_is_mounted()) - pstore_mkfile(PSTORE_TYPE_DMESG, psinfo->name, id, psinfo->buf, - size, CURRENT_TIME, psinfo); - spin_unlock_irqrestore(&psinfo->buf_lock, flags); - - return 0; -} -EXPORT_SYMBOL_GPL(pstore_write); - module_param(backend, charp, 0444); MODULE_PARM_DESC(backend, "Pstore backend to use"); diff --git a/include/linux/pstore.h b/include/linux/pstore.h index 2ca8cde5459d..e1461e143be2 100644 --- a/include/linux/pstore.h +++ b/include/linux/pstore.h @@ -22,6 +22,9 @@ #ifndef _LINUX_PSTORE_H #define _LINUX_PSTORE_H +#include +#include + /* types */ enum pstore_type_id { PSTORE_TYPE_DMESG = 0, @@ -41,7 +44,8 @@ struct pstore_info { ssize_t (*read)(u64 *id, enum pstore_type_id *type, struct timespec *time, char **buf, struct pstore_info *psi); - int (*write)(enum pstore_type_id type, u64 *id, + int (*write)(enum pstore_type_id type, + enum kmsg_dump_reason reason, u64 *id, unsigned int part, size_t size, struct pstore_info *psi); int (*erase)(enum pstore_type_id type, u64 id, struct pstore_info *psi); @@ -50,18 +54,12 @@ struct pstore_info { #ifdef CONFIG_PSTORE extern int pstore_register(struct pstore_info *); -extern int pstore_write(enum pstore_type_id type, char *buf, size_t size); #else static inline int pstore_register(struct pstore_info *psi) { return -ENODEV; } -static inline int -pstore_write(enum pstore_type_id type, char *buf, size_t size) -{ - return -ENODEV; -} #endif #endif /*_LINUX_PSTORE_H*/ -- cgit v1.2.3 From 2174f6df7891fa331800beb72634c969f017900b Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Nov 2011 13:49:00 -0800 Subject: pstore: gracefully handle NULL pstore_info functions If a pstore backend doesn't want to support various portions of the pstore interface, it can just leave those functions NULL instead of creating no-op stubs. Signed-off-by: Kees Cook Signed-off-by: Tony Luck --- fs/pstore/inode.c | 3 ++- fs/pstore/platform.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'fs/pstore/platform.c') diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index 379a02dc1217..b3b426edb2fd 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c @@ -80,7 +80,8 @@ static int pstore_unlink(struct inode *dir, struct dentry *dentry) { struct pstore_private *p = dentry->d_inode->i_private; - p->psi->erase(p->type, p->id, p->psi); + if (p->psi->erase) + p->psi->erase(p->type, p->id, p->psi); return simple_unlink(dir, dentry); } diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index f146d89179bf..9ec22d3b4293 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -207,8 +207,7 @@ void pstore_get_records(int quiet) return; mutex_lock(&psi->read_mutex); - rc = psi->open(psi); - if (rc) + if (psi->open && psi->open(psi)) goto out; while ((size = psi->read(&id, &type, &time, &buf, psi)) > 0) { @@ -219,7 +218,8 @@ void pstore_get_records(int quiet) if (rc && (rc != -EEXIST || !quiet)) failed++; } - psi->close(psi); + if (psi->close) + psi->close(psi); out: mutex_unlock(&psi->read_mutex); -- cgit v1.2.3