Skip to content

Commit a83c68a

Browse files
anarsoulrafaeljw
authored andcommitted
ACPI: FPDT: properly handle invalid FPDT subtables
Buggy BIOSes may have invalid FPDT subtables, e.g. on my hardware: S3PT subtable: 7F20FE30: 53 33 50 54 24 00 00 00-00 00 00 00 00 00 18 01 *S3PT$...........* 7F20FE40: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 *................* 7F20FE50: 00 00 00 00 Here the first record has zero length. FBPT subtable: 7F20FE50: 46 42 50 54-3C 00 00 00 46 42 50 54 *....FBPT<...FBPT* 7F20FE60: 02 00 30 02 00 00 00 00-00 00 00 00 00 00 00 00 *..0.............* 7F20FE70: 2A A6 BC 6E 0B 00 00 00-1A 44 41 70 0B 00 00 00 **..n.....DAp....* 7F20FE80: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 *................* And here FBPT table has FBPT signature repeated instead of the first record. Current code will be looping indefinitely due to zero length records, so break out of the loop if record length is zero. While we are here, add proper handling for fpdt_process_subtable() failures. Fixes: d1eb86e ("ACPI: tables: introduce support for FPDT table") Cc: All applicable <[email protected]> Signed-off-by: Vasily Khoruzhick <[email protected]> [ rjw: Comment edit, added empty code lines ] Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 8a749fd commit a83c68a

File tree

1 file changed

+37
-8
lines changed

1 file changed

+37
-8
lines changed

drivers/acpi/acpi_fpdt.c

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,19 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type)
194194
record_header = (void *)subtable_header + offset;
195195
offset += record_header->length;
196196

197+
if (!record_header->length) {
198+
pr_err(FW_BUG "Zero-length record found in FPTD.\n");
199+
result = -EINVAL;
200+
goto err;
201+
}
202+
197203
switch (record_header->type) {
198204
case RECORD_S3_RESUME:
199205
if (subtable_type != SUBTABLE_S3PT) {
200206
pr_err(FW_BUG "Invalid record %d for subtable %s\n",
201207
record_header->type, signature);
202-
return -EINVAL;
208+
result = -EINVAL;
209+
goto err;
203210
}
204211
if (record_resume) {
205212
pr_err("Duplicate resume performance record found.\n");
@@ -208,7 +215,7 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type)
208215
record_resume = (struct resume_performance_record *)record_header;
209216
result = sysfs_create_group(fpdt_kobj, &resume_attr_group);
210217
if (result)
211-
return result;
218+
goto err;
212219
break;
213220
case RECORD_S3_SUSPEND:
214221
if (subtable_type != SUBTABLE_S3PT) {
@@ -223,13 +230,14 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type)
223230
record_suspend = (struct suspend_performance_record *)record_header;
224231
result = sysfs_create_group(fpdt_kobj, &suspend_attr_group);
225232
if (result)
226-
return result;
233+
goto err;
227234
break;
228235
case RECORD_BOOT:
229236
if (subtable_type != SUBTABLE_FBPT) {
230237
pr_err(FW_BUG "Invalid %d for subtable %s\n",
231238
record_header->type, signature);
232-
return -EINVAL;
239+
result = -EINVAL;
240+
goto err;
233241
}
234242
if (record_boot) {
235243
pr_err("Duplicate boot performance record found.\n");
@@ -238,7 +246,7 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type)
238246
record_boot = (struct boot_performance_record *)record_header;
239247
result = sysfs_create_group(fpdt_kobj, &boot_attr_group);
240248
if (result)
241-
return result;
249+
goto err;
242250
break;
243251

244252
default:
@@ -247,6 +255,18 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type)
247255
}
248256
}
249257
return 0;
258+
259+
err:
260+
if (record_boot)
261+
sysfs_remove_group(fpdt_kobj, &boot_attr_group);
262+
263+
if (record_suspend)
264+
sysfs_remove_group(fpdt_kobj, &suspend_attr_group);
265+
266+
if (record_resume)
267+
sysfs_remove_group(fpdt_kobj, &resume_attr_group);
268+
269+
return result;
250270
}
251271

252272
static int __init acpi_init_fpdt(void)
@@ -255,6 +275,7 @@ static int __init acpi_init_fpdt(void)
255275
struct acpi_table_header *header;
256276
struct fpdt_subtable_entry *subtable;
257277
u32 offset = sizeof(*header);
278+
int result;
258279

259280
status = acpi_get_table(ACPI_SIG_FPDT, 0, &header);
260281

@@ -263,17 +284,19 @@ static int __init acpi_init_fpdt(void)
263284

264285
fpdt_kobj = kobject_create_and_add("fpdt", acpi_kobj);
265286
if (!fpdt_kobj) {
266-
acpi_put_table(header);
267-
return -ENOMEM;
287+
result = -ENOMEM;
288+
goto err_nomem;
268289
}
269290

270291
while (offset < header->length) {
271292
subtable = (void *)header + offset;
272293
switch (subtable->type) {
273294
case SUBTABLE_FBPT:
274295
case SUBTABLE_S3PT:
275-
fpdt_process_subtable(subtable->address,
296+
result = fpdt_process_subtable(subtable->address,
276297
subtable->type);
298+
if (result)
299+
goto err_subtable;
277300
break;
278301
default:
279302
/* Other types are reserved in ACPI 6.4 spec. */
@@ -282,6 +305,12 @@ static int __init acpi_init_fpdt(void)
282305
offset += sizeof(*subtable);
283306
}
284307
return 0;
308+
err_subtable:
309+
kobject_put(fpdt_kobj);
310+
311+
err_nomem:
312+
acpi_put_table(header);
313+
return result;
285314
}
286315

287316
fs_initcall(acpi_init_fpdt);

0 commit comments

Comments
 (0)