Received: by 2002:a25:c205:0:0:0:0:0 with SMTP id s5csp4646336ybf; Wed, 4 Mar 2020 07:59:21 -0800 (PST) X-Google-Smtp-Source: ADFU+vuzxIIOqzpUhMe1UTUX7E/vxbqUdPiqmBYYKJFylHAfj/X2bgqwipaSpqC9st05AXW4tTj+ X-Received: by 2002:a05:6830:18eb:: with SMTP id d11mr3044927otf.203.1583337561299; Wed, 04 Mar 2020 07:59:21 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1583337561; cv=none; d=google.com; s=arc-20160816; b=j55x9y0icsXeGwXVx99BiL7TtJ1E+5Aad8s5JFZeNf1lc9WVyFaWtLiVG2zD8PgmlF tlUwagK/PkUzFoOfRODTuTD4ZXNIa+IAVXnZXzxIE8kX/xY7+OjpWn/upeg+183JDCoP m1W+cn7vG8JygiFoqjl0xl1TFPb46wicQbsAJI+Lxk5glxLLy40j57e/t5Zz3bM1U6Lw xkGh9SCKfnwA5Tdlg8SOGcw3VHeJYoCsnot5+U8jKZm1R/396Oiy8PtPgsH5xQuWGvWU 1/2HflbRt4b40CPE4NpxjxYr4N+Bx0NBGGKqnfgNNR3Vy18Vtb1id9KcCdNJmSb2AQ4i dQnw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=GgCQMhvC+J8VjNCF3OkEvuMm+dXzdLLaKktJeZgwchI=; b=ZVT0XtYGI/KM9aAerho1SyOFY6ZLKP3QjiUtktShyZVZqI1z6oK3V4kYee/XbeH8AK HLFnFQWJbaPPGoKfpUL9YLyQPtrJl0u50woBuFy2DQkfD6YxDs5v+ROfYiWvbgrasuk2 eoMO2arxiYFAzDXLcyZMlgcgslSkOZJe7IUgQlabatG3IUaHEJKhYKk6nDCWZfX8ipvN kBGkgi2Jg0buwcySYaNgkNdJpS86YdUaCLm+C+beXQ1Qf6z8zfQYBuraX1fLoHNAR3PM JgQmwM08Mf0zPHrwY2vJBfrEwAbVD5wsVg+9LrgWh9Ff6J66INt/ZdZXNgdJGu4ZVgFK NRuA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=LHgNltKL; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 9si1234823oix.239.2020.03.04.07.59.08; Wed, 04 Mar 2020 07:59:21 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=LHgNltKL; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387497AbgCDP5b (ORCPT + 99 others); Wed, 4 Mar 2020 10:57:31 -0500 Received: from mail.kernel.org ([198.145.29.99]:49368 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727709AbgCDP5b (ORCPT ); Wed, 4 Mar 2020 10:57:31 -0500 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id 53EAE2166E for ; Wed, 4 Mar 2020 15:57:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1583337450; bh=ILnFl8RmIMsml+ikE7ngdhxT/GrRssw7KpYC8Sdzi8A=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=LHgNltKLgLHu9Ig8V+Tymy7PM5UmFZLeJFrCSpeBxRS8h2S5WWK8NrOToq7JPIh1v DRJQq6l36pq5/fnzzAgbXvIS/wD+wQXt0iNff51CA4syuSvvN/2QTmKEaZr6fUlP7r ZsYiljIwdyxcrWvuvk7EmasRmOEXeg+XZV2CozOA= Received: by mail-wm1-f50.google.com with SMTP id a132so2688737wme.1 for ; Wed, 04 Mar 2020 07:57:30 -0800 (PST) X-Gm-Message-State: ANhLgQ2T2vqXstKbp452if79bvoQMgRNt1YCdVCEr+TV3Pv0pe19CNoL LG8dSi4zabc8kHrH7bXFWmsW2yP+6fDVoz+jNWQC2w== X-Received: by 2002:a1c:2d88:: with SMTP id t130mr4576821wmt.68.1583337448738; Wed, 04 Mar 2020 07:57:28 -0800 (PST) MIME-Version: 1.0 References: <20200304154936.24206-1-vdronov@redhat.com> In-Reply-To: <20200304154936.24206-1-vdronov@redhat.com> From: Ard Biesheuvel Date: Wed, 4 Mar 2020 16:57:16 +0100 X-Gmail-Original-Message-ID: Message-ID: Subject: Re: [PATCH v2] efi: fix a race and a buffer overflow while reading efivars via sysfs To: Vladis Dronov Cc: linux-efi , joeyli , Linux Kernel Mailing List Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Wed, 4 Mar 2020 at 16:50, Vladis Dronov wrote: > > There is a race and a buffer overflow corrupting a kernel memory while > reading an efi variable with a size more than 1024 bytes via the older > sysfs method. This happens because accessing struct efi_variable in > efivar_{attr,size,data}_read() and friends is not protected from > a concurrent access leading to a kernel memory corruption and, at best, > to a crash. The race scenario is the following: > > CPU0: CPU1: > efivar_attr_read() > var->DataSize = 1024; > efivar_entry_get(... &var->DataSize) > down_interruptible(&efivars_lock) > efivar_attr_read() // same efi var > var->DataSize = 1024; > efivar_entry_get(... &var->DataSize) > down_interruptible(&efivars_lock) > virt_efi_get_variable() > // returns EFI_BUFFER_TOO_SMALL but > // var->DataSize is set to a real > // var size more than 1024 bytes > up(&efivars_lock) > virt_efi_get_variable() > // called with var->DataSize set > // to a real var size, returns > // successfully and overwrites > // a 1024-bytes kernel buffer > up(&efivars_lock) > > This can be reproduced by concurrent reading of an efi variable which size > is more than 1024 bytes: > > ts# for cpu in $(seq 0 $(nproc --ignore=1)); do ( taskset -c $cpu \ > cat /sys/firmware/efi/vars/KEKDefault*/size & ) ; done > > Fix this by using a local variable for a var's data buffer size so it > does not get overwritten. Also add a sanity check to efivar_store_raw(). > > Reported-by: Bob Sanders and the LTP testsuite > Signed-off-by: Vladis Dronov > --- > drivers/firmware/efi/efi-pstore.c | 2 +- > drivers/firmware/efi/efivars.c | 32 ++++++++++++++++++++++--------- > drivers/firmware/efi/vars.c | 2 +- > 3 files changed, 25 insertions(+), 11 deletions(-) > > diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c > index 9ea13e8d12ec..e4767a7ce973 100644 > --- a/drivers/firmware/efi/efi-pstore.c > +++ b/drivers/firmware/efi/efi-pstore.c > @@ -161,7 +161,7 @@ static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos, > * > * @record: pstore record to pass to callback > * > - * You MUST call efivar_enter_iter_begin() before this function, and > + * You MUST call efivar_entry_iter_begin() before this function, and > * efivar_entry_iter_end() afterwards. > * > */ This hunk can be dropped now, I guess > diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c > index 7576450c8254..16a617f9c5cf 100644 > --- a/drivers/firmware/efi/efivars.c > +++ b/drivers/firmware/efi/efivars.c > @@ -83,13 +83,16 @@ static ssize_t > efivar_attr_read(struct efivar_entry *entry, char *buf) > { > struct efi_variable *var = &entry->var; > + unsigned long size = sizeof(var->Data); > char *str = buf; > + int ret; > > if (!entry || !buf) > return -EINVAL; > > - var->DataSize = 1024; > - if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) > + ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data); > + var->DataSize = size; For my understanding, could you explain why we do the assignment here? Does var->DataSize matter in this case? Can it deviate from 1024? > + if (ret) > return -EIO; > > if (var->Attributes & EFI_VARIABLE_NON_VOLATILE) > @@ -116,13 +119,16 @@ static ssize_t > efivar_size_read(struct efivar_entry *entry, char *buf) > { > struct efi_variable *var = &entry->var; > + unsigned long size = sizeof(var->Data); > char *str = buf; > + int ret; > > if (!entry || !buf) > return -EINVAL; > > - var->DataSize = 1024; > - if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) > + ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data); > + var->DataSize = size; > + if (ret) > return -EIO; > > str += sprintf(str, "0x%lx\n", var->DataSize); > @@ -133,12 +139,15 @@ static ssize_t > efivar_data_read(struct efivar_entry *entry, char *buf) > { > struct efi_variable *var = &entry->var; > + unsigned long size = sizeof(var->Data); > + int ret; > > if (!entry || !buf) > return -EINVAL; > > - var->DataSize = 1024; > - if (efivar_entry_get(entry, &var->Attributes, &var->DataSize, var->Data)) > + ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data); > + var->DataSize = size; > + if (ret) > return -EIO; > > memcpy(buf, var->Data, var->DataSize); > @@ -199,6 +208,9 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) > u8 *data; > int err; > > + if (!entry || !buf) > + return -EINVAL; > + So what are we sanity checking here? When might this occur? Does it need to be in the same patch? > if (in_compat_syscall()) { > struct compat_efi_variable *compat; > > @@ -250,14 +262,16 @@ efivar_show_raw(struct efivar_entry *entry, char *buf) > { > struct efi_variable *var = &entry->var; > struct compat_efi_variable *compat; > + unsigned long datasize = sizeof(var->Data); > size_t size; > + int ret; > > if (!entry || !buf) > return 0; > > - var->DataSize = 1024; > - if (efivar_entry_get(entry, &entry->var.Attributes, > - &entry->var.DataSize, entry->var.Data)) > + ret = efivar_entry_get(entry, &var->Attributes, &datasize, var->Data); > + var->DataSize = size; > + if (ret) > return -EIO; > > if (in_compat_syscall()) { > diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c > index 436d1776bc7b..5f2a4d162795 100644 > --- a/drivers/firmware/efi/vars.c > +++ b/drivers/firmware/efi/vars.c > @@ -1071,7 +1071,7 @@ EXPORT_SYMBOL_GPL(efivar_entry_iter_end); > * entry on the list. It is safe for @func to remove entries in the > * list via efivar_entry_delete(). > * > - * You MUST call efivar_enter_iter_begin() before this function, and > + * You MUST call efivar_entry_iter_begin() before this function, and > * efivar_entry_iter_end() afterwards. > * > * It is possible to begin iteration from an arbitrary entry within We can drop this. > -- > 2.20.1 >