Skip to content

Commit efda1b5

Browse files
committed
acpi, nfit, libnvdimm: fix / harden ars_status output length handling
Given ambiguities in the ACPI 6.1 definition of the "Output (Size)" field of the ARS (Address Range Scrub) Status command, a firmware implementation may in practice return 0, 4, or 8 to indicate that there is no output payload to process. The specification states "Size of Output Buffer in bytes, including this field.". However, 'Output Buffer' is also the name of the entire payload, and earlier in the specification it states "Max Query ARS Status Output Buffer Size: Maximum size of buffer (including the Status and Extended Status fields)". Without this fix if the BIOS happens to return 0 it causes memory corruption as evidenced by this result from the acpi_nfit_ctl() unit test. ars_status00000000: 00020000 00000000 ........ BUG: stack guard page was hit at ffffc90001750000 (stack is ffffc9000174c000..ffffc9000174ffff) kernel stack overflow (page fault): 0000 [#1] SMP DEBUG_PAGEALLOC task: ffff8803332d2ec0 task.stack: ffffc9000174c000 RIP: 0010:[<ffffffff814cfe72>] [<ffffffff814cfe72>] __memcpy+0x12/0x20 RSP: 0018:ffffc9000174f9a8 EFLAGS: 00010246 RAX: ffffc9000174fab8 RBX: 0000000000000000 RCX: 000000001fffff56 RDX: 0000000000000000 RSI: ffff8803231f5a08 RDI: ffffc90001750000 RBP: ffffc9000174fa88 R08: ffffc9000174fab0 R09: ffff8803231f54b8 R10: 0000000000000008 R11: 0000000000000001 R12: 0000000000000000 R13: 0000000000000000 R14: 0000000000000003 R15: ffff8803231f54a0 FS: 00007f3a611af640(0000) GS:ffff88033ed00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffc90001750000 CR3: 0000000325b20000 CR4: 00000000000406e0 Stack: ffffffffa00bc60d 0000000000000008 ffffc90000000001 ffffc9000174faac 0000000000000292 ffffffffa00c24e4 ffffffffa00c2914 0000000000000000 0000000000000000 ffffffff00000003 ffff880331ae8ad0 0000000800000246 Call Trace: [<ffffffffa00bc60d>] ? acpi_nfit_ctl+0x49d/0x750 [nfit] [<ffffffffa01f4fe0>] nfit_test_probe+0x670/0xb1b [nfit_test] Cc: <[email protected]> Fixes: 747ffe1 ("libnvdimm, tools/testing/nvdimm: fix 'ars_status' output buffer sizing") Signed-off-by: Dan Williams <[email protected]>
1 parent 9a901f5 commit efda1b5

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed

drivers/acpi/nfit/core.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc,
298298

299299
for (i = 0, offset = 0; i < desc->out_num; i++) {
300300
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i, buf,
301-
(u32 *) out_obj->buffer.pointer);
301+
(u32 *) out_obj->buffer.pointer,
302+
out_obj->buffer.length - offset);
302303

303304
if (offset + out_size > out_obj->buffer.length) {
304305
dev_dbg(dev, "%s:%s output object underflow cmd: %s field: %d\n",

drivers/nvdimm/bus.c

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(nd_cmd_in_size);
715715

716716
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
717717
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
718-
const u32 *out_field)
718+
const u32 *out_field, unsigned long remainder)
719719
{
720720
if (idx >= desc->out_num)
721721
return UINT_MAX;
@@ -727,9 +727,24 @@ u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
727727
return in_field[1];
728728
else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
729729
return out_field[1];
730-
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
731-
return out_field[1] - 8;
732-
else if (cmd == ND_CMD_CALL) {
730+
else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2) {
731+
/*
732+
* Per table 9-276 ARS Data in ACPI 6.1, out_field[1] is
733+
* "Size of Output Buffer in bytes, including this
734+
* field."
735+
*/
736+
if (out_field[1] < 4)
737+
return 0;
738+
/*
739+
* ACPI 6.1 is ambiguous if 'status' is included in the
740+
* output size. If we encounter an output size that
741+
* overshoots the remainder by 4 bytes, assume it was
742+
* including 'status'.
743+
*/
744+
if (out_field[1] - 8 == remainder)
745+
return remainder;
746+
return out_field[1] - 4;
747+
} else if (cmd == ND_CMD_CALL) {
733748
struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;
734749

735750
return pkg->nd_size_out;
@@ -876,7 +891,7 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
876891
/* process an output envelope */
877892
for (i = 0; i < desc->out_num; i++) {
878893
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i,
879-
(u32 *) in_env, (u32 *) out_env);
894+
(u32 *) in_env, (u32 *) out_env, 0);
880895
u32 copy;
881896

882897
if (out_size == UINT_MAX) {

include/linux/libnvdimm.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
143143
const struct nd_cmd_desc *desc, int idx, void *buf);
144144
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
145145
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
146-
const u32 *out_field);
146+
const u32 *out_field, unsigned long remainder);
147147
int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count);
148148
struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
149149
struct nd_region_desc *ndr_desc);

0 commit comments

Comments
 (0)