Skip to content

Commit f6f8285

Browse files
keesaegl
authored andcommitted
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 <[email protected]> Signed-off-by: Tony Luck <[email protected]>
1 parent cfcfc9e commit f6f8285

File tree

4 files changed

+40
-17
lines changed

4 files changed

+40
-17
lines changed

drivers/acpi/apei/erst.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,8 @@ static int erst_check_table(struct acpi_table_erst *erst_tab)
932932
static int erst_open_pstore(struct pstore_info *psi);
933933
static int erst_close_pstore(struct pstore_info *psi);
934934
static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
935-
struct timespec *time, struct pstore_info *psi);
935+
struct timespec *time, char **buf,
936+
struct pstore_info *psi);
936937
static int erst_writer(enum pstore_type_id type, u64 *id, unsigned int part,
937938
size_t size, struct pstore_info *psi);
938939
static int erst_clearer(enum pstore_type_id type, u64 id,
@@ -986,40 +987,51 @@ static int erst_close_pstore(struct pstore_info *psi)
986987
}
987988

988989
static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
989-
struct timespec *time, struct pstore_info *psi)
990+
struct timespec *time, char **buf,
991+
struct pstore_info *psi)
990992
{
991993
int rc;
992994
ssize_t len = 0;
993995
u64 record_id;
994-
struct cper_pstore_record *rcd = (struct cper_pstore_record *)
995-
(erst_info.buf - sizeof(*rcd));
996+
struct cper_pstore_record *rcd;
997+
size_t rcd_len = sizeof(*rcd) + erst_info.bufsize;
996998

997999
if (erst_disable)
9981000
return -ENODEV;
9991001

1002+
rcd = kmalloc(rcd_len, GFP_KERNEL);
1003+
if (!rcd) {
1004+
rc = -ENOMEM;
1005+
goto out;
1006+
}
10001007
skip:
10011008
rc = erst_get_record_id_next(&reader_pos, &record_id);
10021009
if (rc)
10031010
goto out;
10041011

10051012
/* no more record */
10061013
if (record_id == APEI_ERST_INVALID_RECORD_ID) {
1007-
rc = -1;
1014+
rc = -EINVAL;
10081015
goto out;
10091016
}
10101017

1011-
len = erst_read(record_id, &rcd->hdr, sizeof(*rcd) +
1012-
erst_info.bufsize);
1018+
len = erst_read(record_id, &rcd->hdr, rcd_len);
10131019
/* The record may be cleared by others, try read next record */
10141020
if (len == -ENOENT)
10151021
goto skip;
1016-
else if (len < 0) {
1017-
rc = -1;
1022+
else if (len < sizeof(*rcd)) {
1023+
rc = -EIO;
10181024
goto out;
10191025
}
10201026
if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0)
10211027
goto skip;
10221028

1029+
*buf = kmalloc(len, GFP_KERNEL);
1030+
if (*buf == NULL) {
1031+
rc = -ENOMEM;
1032+
goto out;
1033+
}
1034+
memcpy(*buf, rcd->data, len - sizeof(*rcd));
10231035
*id = record_id;
10241036
if (uuid_le_cmp(rcd->sec_hdr.section_type,
10251037
CPER_SECTION_TYPE_DMESG) == 0)
@@ -1037,6 +1049,7 @@ static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
10371049
time->tv_nsec = 0;
10381050

10391051
out:
1052+
kfree(rcd);
10401053
return (rc < 0) ? rc : (len - sizeof(*rcd));
10411054
}
10421055

drivers/firmware/efivars.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,8 @@ static int efi_pstore_close(struct pstore_info *psi)
457457
}
458458

459459
static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
460-
struct timespec *timespec, struct pstore_info *psi)
460+
struct timespec *timespec,
461+
char **buf, struct pstore_info *psi)
461462
{
462463
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
463464
struct efivars *efivars = psi->data;
@@ -478,7 +479,11 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
478479
timespec->tv_nsec = 0;
479480
get_var_data_locked(efivars, &efivars->walk_entry->var);
480481
size = efivars->walk_entry->var.DataSize;
481-
memcpy(psi->buf, efivars->walk_entry->var.Data, size);
482+
*buf = kmalloc(size, GFP_KERNEL);
483+
if (*buf == NULL)
484+
return -ENOMEM;
485+
memcpy(*buf, efivars->walk_entry->var.Data,
486+
size);
482487
efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
483488
struct efivar_entry, list);
484489
return size;

fs/pstore/platform.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ int pstore_register(struct pstore_info *psi)
167167
}
168168

169169
psinfo = psi;
170+
mutex_init(&psinfo->read_mutex);
170171
spin_unlock(&pstore_lock);
171172

172173
if (owner && !try_module_get(owner)) {
@@ -195,30 +196,32 @@ EXPORT_SYMBOL_GPL(pstore_register);
195196
void pstore_get_records(int quiet)
196197
{
197198
struct pstore_info *psi = psinfo;
199+
char *buf = NULL;
198200
ssize_t size;
199201
u64 id;
200202
enum pstore_type_id type;
201203
struct timespec time;
202204
int failed = 0, rc;
203-
unsigned long flags;
204205

205206
if (!psi)
206207
return;
207208

208-
spin_lock_irqsave(&psinfo->buf_lock, flags);
209+
mutex_lock(&psi->read_mutex);
209210
rc = psi->open(psi);
210211
if (rc)
211212
goto out;
212213

213-
while ((size = psi->read(&id, &type, &time, psi)) > 0) {
214-
rc = pstore_mkfile(type, psi->name, id, psi->buf, (size_t)size,
214+
while ((size = psi->read(&id, &type, &time, &buf, psi)) > 0) {
215+
rc = pstore_mkfile(type, psi->name, id, buf, (size_t)size,
215216
time, psi);
217+
kfree(buf);
218+
buf = NULL;
216219
if (rc && (rc != -EEXIST || !quiet))
217220
failed++;
218221
}
219222
psi->close(psi);
220223
out:
221-
spin_unlock_irqrestore(&psinfo->buf_lock, flags);
224+
mutex_unlock(&psi->read_mutex);
222225

223226
if (failed)
224227
printk(KERN_WARNING "pstore: failed to load %d record(s) from '%s'\n",

include/linux/pstore.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ struct pstore_info {
3535
spinlock_t buf_lock; /* serialize access to 'buf' */
3636
char *buf;
3737
size_t bufsize;
38+
struct mutex read_mutex; /* serialize open/read/close */
3839
int (*open)(struct pstore_info *psi);
3940
int (*close)(struct pstore_info *psi);
4041
ssize_t (*read)(u64 *id, enum pstore_type_id *type,
41-
struct timespec *time, struct pstore_info *psi);
42+
struct timespec *time, char **buf,
43+
struct pstore_info *psi);
4244
int (*write)(enum pstore_type_id type, u64 *id,
4345
unsigned int part, size_t size, struct pstore_info *psi);
4446
int (*erase)(enum pstore_type_id type, u64 id,

0 commit comments

Comments
 (0)