Received: by 2002:a05:6a10:22f:0:0:0:0 with SMTP id 15csp1567301pxk; Fri, 4 Sep 2020 12:43:39 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxdzXpvvylfr67Mekpx1wji32kBmhTRr4NqFOhWH1AMkleukemuJ9ztLohOiluDizlKIXRV X-Received: by 2002:a05:6402:b06:: with SMTP id bm6mr3038130edb.15.1599248619388; Fri, 04 Sep 2020 12:43:39 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1599248619; cv=none; d=google.com; s=arc-20160816; b=GF1nNHzeJkh/S+YvmYlXqQNWjieOGkZZbqcSmEFAkQpRFQ/o/IoCzsbS3trsgv58z5 YtQgg2Yv4AMj4ATXEjdHZcKDJHbBjKnpwbYqp2S1/UddhN1+V/b+1FXBGP2H4icRW+YY mg5B7EZ0Esay2UFZZSy3jktGZW3UVqZER4E8ab0KEbBFjarowjDqNbahtTTogOWl83gn oxKJTrga55Jvv1HXBycki3Lb437kE7oxZuz//L1eEATJLJtJrmgzc8WUi+vUBESqzxG8 K+IHMf3UJHr6YKV7RzR7wlsNjhwPARg3bdnsKQxwBS1/HTW84EmVOGS93yJKaP6u5Hnr KRdQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:message-id:to:from:date:subject :dkim-signature; bh=YDpvI+E0cXOgckTHy9aJmMvLX1eIuAAIFypiyBK6y9g=; b=GGR8r+DoX+AGEjjmK7/EXBCdMNQ8Y6J5xiRLmtNcuMsnWDrfXhhh+C5aQRe1LrRfym eLxzdgAev/JNtfgFvpCG8nHzWepc+I4D5DUd78ENpDc7bJkg82Ln59SmC9sLIXj+WPTL E9ht5S2qa7V/CshMRk3rAr1mFyJ7jHVoeCX+bCp1AGRCD1ZhJknGZubP9Djpzp/lP54O +Tht2dGty8dS0YJEFqkdRUxRy/ThJc9PIXr7KzMRhQFA3xY+plzgz7JDQ4Qcf/W0OaWb XDzUeo7Xu3PUuTg4O/luIe2cxmwhInIaHcdjH0PwEBBeCTbit4DI0v8QMeTnmwQfvxsO 8lXw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=DOgGEm0i; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id o18si888349edw.582.2020.09.04.12.43.16; Fri, 04 Sep 2020 12:43:39 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=DOgGEm0i; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726135AbgIDTka (ORCPT + 99 others); Fri, 4 Sep 2020 15:40:30 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51822 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726221AbgIDTka (ORCPT ); Fri, 4 Sep 2020 15:40:30 -0400 Received: from mail-ej1-x644.google.com (mail-ej1-x644.google.com [IPv6:2a00:1450:4864:20::644]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AC492C061244; Fri, 4 Sep 2020 12:40:29 -0700 (PDT) Received: by mail-ej1-x644.google.com with SMTP id e23so10097688eja.3; Fri, 04 Sep 2020 12:40:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=subject:date:from:to:message-id; bh=YDpvI+E0cXOgckTHy9aJmMvLX1eIuAAIFypiyBK6y9g=; b=DOgGEm0ibUDVucPZpCSyVP27DZScdmLzl5W7EfZWQ1IlcHmFY15Fb+iCRPxvZnIzAa wZZ5TGqQmw0q7PkZlucUnV1hghtjm4ZwbKcuEm9Fu8Ou+NOerMbQ5sqnRntgmP98taUV 7eEHunAHagDeyhrQlGXRCqgukUCTrTQ3KicKBS2+STYLkS5rGFr6MGM3yJC+lViygwsu vfoDH730blZPujtYl1eNuaXWas6KGsPwcJhsDC0YWbMyz/0LbDh26VRG5uOah3VncVMR Ry8LuPsMlVCyFlEphtXiQoGWKnfdrgZ2NZbixMilfWyJmn/Dn8iREFbaa7p2PrwI21SM O9RQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:date:from:to:message-id; bh=YDpvI+E0cXOgckTHy9aJmMvLX1eIuAAIFypiyBK6y9g=; b=VFWBnQwiWeVXGJFLe0Mr80d47O06vfc5EFt80CIj66NmqCRm6yn+HT3r8FTVeVR8nR 3wLk2Pk+KbYkNcpHG7hsk5oGpdyGOEG+O/NCm2D1CfaOEjc0Zg8FAVhSl8WgDwh5HGlo aNY6v4lY8lVYRmfC1s3qrTRj1t1FilHXqT1YVNaLFWvbVUv3S3sE5u7rZOqTsv8FbkaS dEnAi9MHTO0rHy0qDoFaffL8uZAxh9lsRkHX2Uy8Er3jHWxesqCihDO5pIg37fkuhMqv fxO0BVNX/tzT9X2mha0aeeLjXHIAtM91NDLykOYlOBT9a2AmUKIm2SDCtu2EdnFHsjjk fLcw== X-Gm-Message-State: AOAM533TkBV83cV1VtmFN5zhnh5Igpi05ilvaIjaqSMwL7KEgCDF7Xjp SjWXgscSwsOu9tToUh9TWuCPuDA3TNA/hQ== X-Received: by 2002:a17:907:40c1:: with SMTP id nv1mr2173389ejb.318.1599248427332; Fri, 04 Sep 2020 12:40:27 -0700 (PDT) Received: from gmail.com (tor-exit-15.zbau.f3netze.de. [185.220.100.242]) by smtp.gmail.com with ESMTPSA id i35sm6924825edi.41.2020.09.04.12.40.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2020 12:40:26 -0700 (PDT) Subject: [RFC] openprom: Fix 'opiocnextprop'; ensure integer conversions; use string size Date: Fri, 04 Sep 2020 19:40:00 -0000 From: Michael Witten To: sparclinux@vger.kernel.org, linux-kernel@vger.kernel.org Message-ID: Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The following patch improves the quality and correctness of the openprom code. I have neither a machine to test the result nor a toolchain to compile it, and that is why it is listed currently as an "RFC". Nonetheless, I believe those who do have these tools will find the proposed changes useful; I hope you will help me iterate this patch into something worthy of merging (or use it as the basis for your own changes). Sincerely, Michael Witten --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<-- This commit fixes or improves a conglomeration of related issues: * 'opiocnextprop()' was incorrectly implemented. * Integer conversions were not checked. * Faulty PROMs and users can leave out nul-termination * The confusion of string lengths for string sizes. OPIOCNEXTPROP ============= According to NetBSD's documentation, this ioctl is supposed to return a property's *name*. However, until this commit, it has returned a property's *value*; this commit implements the return of the name. In addition, the 'op_buflen' field is now also updated, whereas before it was not. Conversions =========== There are a number of different integer types used throughout this code to represent the size of some buffer; this commit introduces some checks to help ensure that these conversions will succeed, or at least to help ensure that bad conversions will be handled in a meaningful way. Nul-Termination =============== Faulty PROMs ------------ The Sun/Oracle documentation says that when returning data via the user-supplied buffers, the data should be explicitly nul-terminated if the buffer size is larger than the size of the data, just in case a faulty PROM fails to include nul-termination properly. In the "Sun" ioctls, this should already be handled, because the data structure in question is zeroed when allocated, but this commit also introduces some checks for this when the following kernel config is defined: CONFIG_DEBUG_KERNEL One BSD ioctl was amended to include this automatic nul-termination: OPIOCGET User Error ---------- It is perhaps worth noting that the following helper function was already being used to append a nul character to user data provided to the BSD ioctl implementations: copyin_string() The BSD documentation for those ioctl functions does indeed refer to "counted" strings, which may mean that nul-termination is not required, so the behavior of copyin_string() is merely a defensive way to handle input that is not nul-terminated. Such defensive programming is maintained by this commit; in addition, where applicable, strlen() is used to determine a length, rather than relying on a user-supplied value. String Size =========== There is an important distinction to be made between the following: * A nul-terminated string's size * A nul-terminated string's length This commit tries to make this distinction as much as possible, and assumes that all strings are intended to be nul-terminated. The result is the following: * Sometimes a variable's name is simply changed (e.g., from 'len' to 'size'). * Sometimes 'strlen()' is called rather than relying on some buffer size. * Sometimes, there is the replacement of code that erroneously uses string length rather than string size. All together, these changes make the code more robust and correct. Signed-off-by: Michael Witten --- arch/sparc/include/asm/prom.h | 2 +- arch/sparc/kernel/prom_common.c | 14 +-- drivers/sbus/char/openprom.c | 263 ++++++++++++++++++++++++++++------------ 3 files changed, 194 insertions(+), 85 deletions(-) diff --git a/arch/sparc/include/asm/prom.h b/arch/sparc/include/asm/prom.h index 587edb8b5a65..b523dab16877 100644 --- a/arch/sparc/include/asm/prom.h +++ b/arch/sparc/include/asm/prom.h @@ -30,7 +30,7 @@ struct of_irq_controller { }; struct device_node *of_find_node_by_cpuid(int cpuid); -int of_set_property(struct device_node *node, const char *name, void *val, int len); +int of_set_property(struct device_node *node, const char *name, void *val, int size); extern struct mutex of_set_property_mutex; int of_getintprop_default(struct device_node *np, const char *name, diff --git a/arch/sparc/kernel/prom_common.c b/arch/sparc/kernel/prom_common.c index c9ec70888a39..fbc96c6e554c 100644 --- a/arch/sparc/kernel/prom_common.c +++ b/arch/sparc/kernel/prom_common.c @@ -34,10 +34,10 @@ EXPORT_SYMBOL(of_console_options); int of_getintprop_default(struct device_node *np, const char *name, int def) { struct property *prop; - int len; + int size; - prop = of_find_property(np, name, &len); - if (!prop || len != 4) + prop = of_find_property(np, name, &size); + if (!prop || size != 4) return def; return *(int *) prop->value; @@ -47,14 +47,14 @@ EXPORT_SYMBOL(of_getintprop_default); DEFINE_MUTEX(of_set_property_mutex); EXPORT_SYMBOL(of_set_property_mutex); -int of_set_property(struct device_node *dp, const char *name, void *val, int len) +int of_set_property(struct device_node *dp, const char *name, void *val, int size) { struct property **prevp; unsigned long flags; void *new_val; int err; - new_val = kmemdup(val, len, GFP_KERNEL); + new_val = kmemdup(val, size, GFP_KERNEL); if (!new_val) return -ENOMEM; @@ -70,12 +70,12 @@ int of_set_property(struct device_node *dp, const char *name, void *val, int len void *old_val = prop->value; int ret; - ret = prom_setprop(dp->phandle, name, val, len); + ret = prom_setprop(dp->phandle, name, val, size); err = -EINVAL; if (ret >= 0) { prop->value = new_val; - prop->length = len; + prop->length = size; if (OF_IS_DYNAMIC(prop)) kfree(old_val); diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c index 30b9751aad30..9bc2877aa09a 100644 --- a/drivers/sbus/char/openprom.c +++ b/drivers/sbus/char/openprom.c @@ -16,6 +16,7 @@ */ +#include #include #include #include @@ -54,42 +55,76 @@ static struct device_node *options_node; * Copy an openpromio structure into kernel space from user space. * This routine does error checking to make sure that all memory * accesses are within bounds. A pointer to the allocated openpromio - * structure will be placed in "*opp_p". Return value is the length - * of the user supplied buffer. + * structure will be placed in "*opp_p". The return value is the size + * of the user supplied buffer; the return value is negative on error. */ -static int copyin(struct openpromio __user *info, struct openpromio **opp_p) +static ssize_t copyin(struct openpromio __user *info, struct openpromio **opp_p) { - unsigned int bufsize; + unsigned int oprom_size; + size_t bufsize; + ssize_t result; if (!info || !opp_p) return -EFAULT; - if (get_user(bufsize, &info->oprom_size)) + if (get_user(oprom_size, &info->oprom_size)) return -EFAULT; - if (bufsize == 0) + if (oprom_size == 0) return -EINVAL; + static_assert(SIZE_MAX >= UINT_MAX); + /* If the bufsize is too large, just limit it. * Fix from Jason Rappleye. */ - if (bufsize > OPROMMAXPARAM) - bufsize = OPROMMAXPARAM; + #if UINT_MAX <= OPROMMAXPARAM + bufsize = oprom_size; + #else + if (oprom_size <= OPROMMAXPARAM) + bufsize = oprom_size; + else + bufsize = OPROMMAXPARAM; + #endif + + #if CONFIG_DEBUG_KERNEL + if (WARN_ON(bufsize > OPROMMAXPARAM)) + bufsize = OPROMMAXPARAM; + #endif + + #if OPROMMAXPARAM > SIZE_MAX/2 + if (bufsize > SIZE_MAX/2) + return -EFAULT; + #endif + + result = (ssize_t)bufsize; + /* The buffer 'oprom_array' is often supposed to contain a + * nul-terminated string, but the user may have erroneously + * set 'oprom_size' to the length of that string rather than + * the size of that string; therefore, the '+ 1' essentially + * appends a nul character. Any code that expects a string + * to be nul-terminated must not trust the size returned by + * this function. + */ + static_assert(OPROMMAXPARAM <= SIZE_MAX - sizeof(int) - 1); if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL))) return -ENOMEM; + static_assert(ULONG_MAX >= SIZE_MAX); if (copy_from_user(&(*opp_p)->oprom_array, &info->oprom_array, bufsize)) { kfree(*opp_p); return -EFAULT; } - return bufsize; + + return result; } -static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) +static ssize_t getstrings(struct openpromio __user *info, struct openpromio **opp_p) { - int n, bufsize; + int n; + ssize_t bufsize; char c; if (!info || !opp_p) @@ -98,9 +133,8 @@ static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL))) return -ENOMEM; - (*opp_p)->oprom_size = 0; - - n = bufsize = 0; + bufsize = n = 0; + static_assert((SIZE_MAX/2) >= OPROMMAXPARAM); while ((n < 2) && (bufsize < OPROMMAXPARAM)) { if (get_user(c, &info->oprom_array[bufsize])) { kfree(*opp_p); @@ -120,69 +154,109 @@ static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) /* * Copy an openpromio structure in kernel space back to user space. */ -static int copyout(void __user *info, struct openpromio *opp, int len) +static int copyout(void __user *info, struct openpromio *opp, const size_t size) { - if (copy_to_user(info, opp, len)) + static_assert(ULONG_MAX >= SIZE_MAX); + if (copy_to_user(info, opp, size)) return -EFAULT; return 0; } -static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, const size_t bufsize) { const void *pval; - int len; + int pval_size; if (!dp || - !(pval = of_get_property(dp, op->oprom_array, &len)) || - len <= 0 || len > bufsize) + !(pval = of_get_property(dp, op->oprom_array, &pval_size)) || + pval_size <= 0 || pval_size > bufsize) { + #ifdef CONFIG_DEBUG_KERNEL + if (WARN_ON(op->oprom_size)) + op->oprom_size = 0; + #endif return copyout(argp, op, sizeof(int)); + } + + op->oprom_size = pval_size; + memcpy(op->oprom_array, pval, pval_size); - memcpy(op->oprom_array, pval, len); - op->oprom_array[len] = '\0'; - op->oprom_size = len; + #ifdef CONFIG_DEBUG_KERNEL + // If the buffer is larger than needed, then + // the contents should be nul-terminated in + // case the PROM erroneously produces a string + // that is not nul-terminated. + if (pval_size < bufsize) + char *const end = op->oprom_array + pval_size; + if (WARN_ON(*end)) + *end = 0; + #endif return copyout(argp, op, sizeof(int) + bufsize); } -static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, const size_t bufsize) { struct property *prop; - int len; + size_t name_size; + unsigned int uint_name_size; if (!dp) - return copyout(argp, op, sizeof(int)); + goto nothing; + if (op->oprom_array[0] == '\0') { prop = dp->properties; - if (!prop) - return copyout(argp, op, sizeof(int)); - len = strlen(prop->name); } else { prop = of_find_property(dp, op->oprom_array, NULL); + if (prop) + prop = prop->next; + } - if (!prop || - !prop->next || - (len = strlen(prop->next->name)) + 1 > bufsize) - return copyout(argp, op, sizeof(int)); + if (!prop) + goto nothing; - prop = prop->next; - } + name_size = 1 + strlen(prop->name); + + uint_name_size = name_size; + if (unlikely(uint_name_size != name_size)) + goto nothing; // overflow + + if (name_size > bufsize) + goto nothing; - memcpy(op->oprom_array, prop->name, len); - op->oprom_array[len] = '\0'; - op->oprom_size = ++len; + memcpy(op->oprom_array, prop->name, name_size); + op->oprom_size = uint_name_size; return copyout(argp, op, sizeof(int) + bufsize); + +nothing: + #ifdef CONFIG_DEBUG_KERNEL + if (WARN_ON(op->oprom_size)) + op-oprom_size = 0; + #endif + return copyout(argp, op, sizeof(int)); } -static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize) +static int opromsetopt(struct device_node *dp, struct openpromio *op, const size_t name_value_size) { - char *buf = op->oprom_array + strlen(op->oprom_array) + 1; - int len = op->oprom_array + bufsize - buf; + const char *const name = op->oprom_array; + const size_t name_size = 1 + strlen(name); + char *const value = op->oprom_array + name_size; + const size_t value_size = name_value_size - name_size; + int int_value_size; + + #if CONFIG_DEBUG_KERNEL + if (WARN_ON(name_value_size <= name_size)) + return -EFAULT; + #endif - return of_set_property(options_node, op->oprom_array, buf, len); + if (unlikely(value_size > INT_MAX)) + return -EINVAL; + int_value_size = (int)value_size; + + return of_set_property(options_node, name, value, int_value_size); } -static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, const size_t bufsize, DATA *data) { phandle ph; @@ -229,7 +303,7 @@ static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp return copyout(argp, op, bufsize + sizeof(int)); } -static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, const size_t bufsize, DATA *data) { int err = -EINVAL; @@ -255,7 +329,7 @@ static int oprompci2node(void __user *argp, struct device_node *dp, struct openp return err; } -static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, const size_t bufsize, DATA *data) { phandle ph = 0; @@ -269,16 +343,24 @@ static int oprompath2node(void __user *argp, struct device_node *dp, struct open return copyout(argp, op, bufsize + sizeof(int)); } -static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize) +static int opromgetbootargs(void __user *argp, struct openpromio *op, const size_t bufsize) { - char *buf = saved_command_line; - int len = strlen(buf); + const char *const buf = saved_command_line; + const size_t size = 1 + strlen(buf); + unsigned int uint_size; + + if (unlikely(size == 0)) + return -EFAULT; // overflow - if (len > bufsize) + if (size > bufsize) return -EINVAL; - strcpy(op->oprom_array, buf); - op->oprom_size = len; + if (unlikely(size > UINT_MAX)) + return -EFAULT; + uint_size = (unsigned int)size; + + memcpy(op->oprom_array, buf, size); + op->oprom_size = uint_size; return copyout(argp, op, bufsize + sizeof(int)); } @@ -292,7 +374,8 @@ static long openprom_sunos_ioctl(struct file * file, { DATA *data = file->private_data; struct openpromio *opp = NULL; - int bufsize, error = 0; + ssize_t bufsize; + int error = 0; static int cnt; void __user *argp = (void __user *)arg; @@ -301,6 +384,12 @@ static long openprom_sunos_ioctl(struct file * file, else bufsize = copyin(argp, &opp); + #ifdef CONFIG_DEBUG_KERNEL + if (WARN_ON(bufsize == 0)) + bufsize = -EFAULT; + #enif + + static_assert(LONG_MIN <= -(SIZE_MAX/2)-1); if (bufsize < 0) return bufsize; @@ -373,7 +462,9 @@ static struct device_node *get_node(phandle n, DATA *data) /* Copy in a whole string from userspace into kernelspace. */ static char * copyin_string(char __user *user, size_t len) { - if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0) + // Is this test even necessary? Surely, memdup_user_nul() + // will fail to allocate space in this condition. + if (len == SIZE_MAX) return ERR_PTR(-EINVAL); return memdup_user_nul(user, len); @@ -388,7 +479,7 @@ static int opiocget(void __user *argp, DATA *data) struct device_node *dp; char *str; const void *pval; - int err, len; + int err, size; if (copy_from_user(&op, argp, sizeof(op))) return -EFAULT; @@ -399,15 +490,21 @@ static int opiocget(void __user *argp, DATA *data) if (IS_ERR(str)) return PTR_ERR(str); - pval = of_get_property(dp, str, &len); + pval = of_get_property(dp, str, &size); err = 0; - if (!pval || len > op.op_buflen) { + if (!pval || size > op.op_buflen) { err = -EINVAL; } else { - op.op_buflen = len; + const int buflen = op.op_buflen; + op.op_buflen = size; if (copy_to_user(argp, &op, sizeof(op)) || - copy_to_user(op.op_buf, pval, len)) + copy_to_user(op.op_buf, pval, size)) err = -EFAULT; + if (size < buflen) { + const char c = 0; + if (copy_to_user(op.op_buf + size, &c, 1)) // To correct faulty PROMs + err = -EFAULT; + } } kfree(str); @@ -420,7 +517,7 @@ static int opiocnextprop(void __user *argp, DATA *data) struct device_node *dp; struct property *prop; char *str; - int len; + size_t name_size; if (copy_from_user(&op, argp, sizeof(op))) return -EFAULT; @@ -443,18 +540,28 @@ static int opiocnextprop(void __user *argp, DATA *data) kfree(str); if (!prop) - len = 0; - else - len = prop->length; + name_size = 0; + else { + name_size = 1 + strlen(prop->name); + if (unlikely(name_size == 0)) + return -EFAULT; // overflow + } + + if (op.op_buflen <= 0) + op.op_buflen = 0; + + if (name_size > op.op_buflen) + name_size = op.op_buflen; + + if (unlikely(name_size > INT_MAX)) + name_size = INT_MAX; - if (len > op.op_buflen) - len = op.op_buflen; + op.op_buflen = (int)name_size; if (copy_to_user(argp, &op, sizeof(op))) return -EFAULT; - if (len && - copy_to_user(op.op_buf, prop->value, len)) + if (name_size && copy_to_user(op.op_buf, prop->name, name_size)) return -EFAULT; return 0; @@ -464,7 +571,7 @@ static int opiocset(void __user *argp, DATA *data) { struct opiocdesc op; struct device_node *dp; - char *str, *tmp; + char *name, *value; int err; if (copy_from_user(&op, argp, sizeof(op))) @@ -474,20 +581,22 @@ static int opiocset(void __user *argp, DATA *data) if (!dp) return -EINVAL; - str = copyin_string(op.op_name, op.op_namelen); - if (IS_ERR(str)) - return PTR_ERR(str); + name = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(name)) + return PTR_ERR(name); - tmp = copyin_string(op.op_buf, op.op_buflen); - if (IS_ERR(tmp)) { - kfree(str); - return PTR_ERR(tmp); + value = copyin_string(op.op_buf, op.op_buflen); + if (IS_ERR(value)) { + kfree(name); + return PTR_ERR(value); } - err = of_set_property(dp, str, tmp, op.op_buflen); + // Note that op.op_buflen is not trusted; strlen() is + // used instead to calculate the proper size. + err = of_set_property(dp, name, value, 1 + strlen(value)); - kfree(str); - kfree(tmp); + kfree(name); + kfree(value); return err; } -- 2.22.0