From: Tycho Andersen <tycho@tycho.ws>
To: linux-sparse@vger.kernel.org, kernel-hardening@lists.openwall.com
Cc: Tycho Andersen <tycho@tycho.ws>
Subject: [RFC v1 4/4] check copy_to_user() sizes
Date: Thu, 20 Dec 2018 12:59:31 -0700 [thread overview]
Message-ID: <20181220195931.20331-5-tycho@tycho.ws> (raw)
In-Reply-To: <20181220195931.20331-1-tycho@tycho.ws>
The intent here is to be a smarter version of the size checking that's just
a hard coded constant. The insight is that we know the type of the thing
that's getting passed to copy_to_user(), so if we can evaluate the size, it
should be <= sizeof(*the thing being copied).
I've run this over x86 with allyes config, and it didn't find anything, so
perhaps it isn't particularly useful. Indeed, most copy_to_user()s look
like:
copy_to_user(p, &foo, sizeof(foo))
so perhaps this isn't a surprise.
The one potentially interesting case is the hard coded array length case:
it's possible someone might change one place but not the other. See e.g.
the first copy_to_user() in drivers/ata/libata-scsi.c, which looks like:
copy_to_user(dst, dev->id, ATA_ID_WORDS * sizeof(u16))
id here is a u16 id[ATA_ID_WORDS], so it would be possible to change this
in one place or not the other. Unfortunately, sparse seems to unify the
type of dev->id to a u16* instead of u16[], so we can't see through this.
Anyway, there are currently no violations of this in the kernel either,
although it does emit some false positives like,
fs/xfs/xfs_ioctl.c:1813:25: warning: copy_to_user() where size (13) is larger than src (1)
due to the above. I wrote the code, so perhaps someday it will be useful to
someone, so I'm posting it :)
Signed-off-by: Tycho Andersen <tycho@tycho.ws>
---
sparse.c | 45 ++++++++++++++++++++++
validation/copy_to_user_sizes.c | 53 ++++++++++++++++++++++++++
validation/copy_to_user_sizes_inline.c | 29 ++++++++++++++
3 files changed, 127 insertions(+)
diff --git a/sparse.c b/sparse.c
index 8bcbe16..eb7d5d7 100644
--- a/sparse.c
+++ b/sparse.c
@@ -31,6 +31,7 @@
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
+#include <limits.h>
#include "lib.h"
#include "allocate.h"
@@ -259,6 +260,49 @@ static void check_no_kernel_pointers(struct position pos, struct expression_list
check_ptr_in_other_as(pos, base, 1);
}
+static void check_copy_size(struct position pos, struct expression_list *args)
+{
+ struct expression *src = ptr_list_nth_entry((struct ptr_list *)args, 1);
+ struct expression *size = ptr_list_nth_entry((struct ptr_list *)args, 2);
+ long long src_actual = LLONG_MAX;
+ long long size_actual;
+ struct symbol *base, *deref;
+
+ /* get the type of src */
+ base = resolve_arg_type(pos, src);
+
+ /* and deref it to *src */
+ deref = base->ctype.base_type;
+
+ /*
+ * Note, this is never hit right now because sparse seems to unify
+ * arrays that are arguments to type *.
+ */
+ if (is_array_type(base)) {
+ long long array_size = get_expression_value_silent(base->array_size);
+ printf("%s is array type\n", show_ident(base->ident));
+
+ /* failed to evaluate */
+ if (array_size == 0)
+ return;
+ src_actual = array_size * bits_to_bytes(deref->bit_size);
+ } else {
+ src_actual = bits_to_bytes(deref->bit_size);
+ }
+
+ /* Evaluate size, if we can */
+ size_actual = get_expression_value_silent(size);
+ /*
+ * size_actual == 0 means that get_expression_value failed; of
+ * course we'll miss something if there is a 0 length copy, but
+ * then nothing will leak anyway so...
+ */
+ if (size_actual == 0)
+ return;
+
+ if (size_actual > src_actual)
+ warning(pos, "copy_to_user() where size (%lld) is larger than src (%lld)", size_actual, src_actual);
+}
#define check_memcpy check_memset
#define check_cfu check_memset
@@ -267,6 +311,7 @@ void check_ctu(struct position pos, struct expression_list *args)
{
check_memset(pos, args);
check_no_kernel_pointers(pos, args);
+ check_copy_size(pos, args);
}
struct checkfn {
diff --git a/validation/copy_to_user_sizes.c b/validation/copy_to_user_sizes.c
new file mode 100644
index 0000000..f1f7b8e
--- /dev/null
+++ b/validation/copy_to_user_sizes.c
@@ -0,0 +1,53 @@
+static void copy_to_user(void *to, const void *from, unsigned long n)
+{
+}
+
+struct foo {
+ int bar;
+};
+
+static void main(void)
+{
+ void *p;
+ struct foo f;
+ int uninitialized;
+
+ copy_to_user(p, &f, sizeof(f));
+ copy_to_user(p, &f, sizeof(f)-1);
+ copy_to_user(p, &f, sizeof(f)+1);
+ copy_to_user(p, &f, 1);
+ copy_to_user(p, &f, 100);
+ copy_to_user(p, &f, uninitialized);
+}
+
+#if 0
+static void broken(void)
+{
+ void *p;
+ char arr[100];
+ struct foo foo_arr[2];
+
+ /*
+ * These all fail right now, because sparse seems to unify the type of
+ * arr/foo_arr to char * /struct foo *, instead of char[]/struct foo[].
+ *
+ * That's unfortunate, because it means that these generate false
+ * positives in the kernel when using a static array length, and that
+ * seems like a case where this type of check would be especially
+ * useful.
+ */
+ copy_to_user(p, arr, 100);
+ copy_to_user(p, arr, 101);
+ copy_to_user(p, foo_arr, sizeof(foo_arr));
+ copy_to_user(p, foo_arr, sizeof(foo_arr)-1);
+ copy_to_user(p, foo_arr, sizeof(foo_arr[0])*2);
+}
+#endif
+/*
+ * check-name: copy_to_user sizes
+ *
+ * check-error-start
+copy_to_user_sizes.c:17:9: warning: copy_to_user() where size (5) is larger than src (4)
+copy_to_user_sizes.c:19:9: warning: copy_to_user() where size (100) is larger than src (4)
+ * check-error-end
+ */
diff --git a/validation/copy_to_user_sizes_inline.c b/validation/copy_to_user_sizes_inline.c
new file mode 100644
index 0000000..bd3c3bd
--- /dev/null
+++ b/validation/copy_to_user_sizes_inline.c
@@ -0,0 +1,29 @@
+static inline void copy_to_user(void *to, const void *from, unsigned long n)
+{
+}
+
+struct foo {
+ int bar;
+};
+
+static void main(void)
+{
+ void *p;
+ struct foo f;
+ int uninitialized;
+
+ copy_to_user(p, &f, sizeof(f));
+ copy_to_user(p, &f, sizeof(f)-1);
+ copy_to_user(p, &f, sizeof(f)+1);
+ copy_to_user(p, &f, 1);
+ copy_to_user(p, &f, 100);
+ copy_to_user(p, &f, uninitialized);
+}
+/*
+ * check-name: copy_to_user sizes
+ *
+ * check-error-start
+copy_to_user_sizes_inline.c:17:21: warning: copy_to_user() where size (5) is larger than src (4)
+copy_to_user_sizes_inline.c:19:21: warning: copy_to_user() where size (100) is larger than src (4)
+ * check-error-end
+ */
--
2.19.1
next prev parent reply other threads:[~2018-12-20 19:59 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-12-20 19:59 [RFC v1 0/4] static analysis of copy_to_user() Tycho Andersen
2018-12-20 19:59 ` [RFC v1 1/4] expression.h: update comment to include other cast types Tycho Andersen
2018-12-20 19:59 ` [RFC v1 2/4] move name-based analysis before linearization Tycho Andersen
2018-12-20 19:59 ` [RFC v1 3/4] add a check for copy_to_user() address spaces Tycho Andersen
2018-12-20 19:59 ` Tycho Andersen [this message]
2018-12-21 22:47 ` [RFC v1 0/4] static analysis of copy_to_user() Luc Van Oostenryck
2019-01-20 19:05 ` Tycho Andersen
2019-01-21 21:41 ` Luc Van Oostenryck
2019-01-24 3:15 ` Tycho Andersen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20181220195931.20331-5-tycho@tycho.ws \
--to=tycho@tycho.ws \
--cc=kernel-hardening@lists.openwall.com \
--cc=linux-sparse@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).