Re: Detecting user data on base types

Andrew Murray <[email protected]>
Newsgroups org.kernel.vger.smatch
Message-ID <[email protected]>
Hi Dan,

Thanks for the quick and helpful response.

On Wed, May 29, 2019 at 10:49:33PM +0300, Dan Carpenter wrote:
> On Wed, May 29, 2019 at 07:47:23PM +0100, Andrew Murray wrote:
> > void func1(int x)
> > {
> > 	x++;
> > 	func2(x);
> > }
> > 
> > void start(void *b)
> > {
> > 	void *a;
> > 	copy_from_user(a, b, 10);
> > 	func1(a[0]);
> > }
> 
> Unfortunately, Smatch is pretty crap at tracking array elements so it
> doesn't track that a[0] is user controlled.
> 
> Otherwise it generally does track that an int is controlled by the user
> and the user can pick a number between 0-10 or whatever.  Like maybe the
> a=0-100 but only for in-kernel API and 0-10 can be set from the ioctl,
> the two ranges are tracked separately.
> 
> > 
> > In the above, smatch seems to be able to identify the callsite for func1 as
> > having user data, however not for x in the x++ expressions. I guess smatch would
> > need to track each use of 'a' and propogate a 'userspace' state for each assignment,
> > e.g. to x. (Is smatch currently limited to tracking user data for pointers rather
> > than base types?).
> 
> x++ is problematic for Smatch, also.  If we're outside of a loop then
> it says 0-10 becomes 1-11, but if we're inside a loop then it becomes
> 1-s32max.

OK thanks for the explanation.

> 
> Another problem is that Smatch doesn't understand how the sign_extend64()
> function works so it doesn't understand the untagged_addr() macro.  :/
> I see one bug here and a missing feature...  Right now Smatch thinks
> that the return value is totally unknown and not user controlled.  This
> is a fixable issue by implementing SPECIAL_LEFTSHIFT in rl_binop().

Actually the ability to treat the return type as not user controlled makes
this a little simpler...

We want to flag a potential issue when a tagged address is used in a function
that wants untagged addresses (as marked by the address space annotation).
Therefore we can simply test for "ctype.as == 5 && is_user_rl(expr)" to meet
this condition.

When a user correctly calls untagged_addr prior to calling these functions
the macro helpfully drops the user controlled flag which stops the condition
above triggering.

Is there a way of getting smatch to drop the 'user controlled' flag without
relying on a bug?

> 
> Anyway, let me send you some updated test code without the array.  Copy
> it to the smatch/ directory and do:
> 
> ./smatch --info -p=kernel test.c | tee warns.txt
> ./smatch_data/db/create_db.sh -p=kernel warns.txt
> ./smatch -p=kernel test.c
> 
> That maybe gives you a better idea of where we're at.

Thanks this was really helpful, I'm not sure I really understood how the
database was created prior to this.

> 
> I guess my other question would be where would you want to print the
> warning?

Thanks to your help I believe I have this working as follows:

diff --git a/check_list.h b/check_list.h
index b1d24c504ba5..f7551e7c5215 100644
--- a/check_list.h
+++ b/check_list.h
@@ -192,6 +192,7 @@ CK(check_nospec_barrier)
 CK(check_spectre)
 CK(check_spectre_second_half)
 CK(check_implicit_dependencies)
+CK(check_tagged)
 
 /* wine specific stuff */
 CK(check_wine_filehandles)
diff --git a/check_tagged.c b/check_tagged.c
new file mode 100644
index 000000000000..d14a81a6c33a
--- /dev/null
+++ b/check_tagged.c
@@ -0,0 +1,49 @@
+#include "smatch.h"
+#include "smatch_extra.h"
+
+static void untagged_check(struct expression *expr)
+{
+       char *name;
+
+       if (parse_error)
+               return;
+
+       if (is_impossible_path())
+               return;
+
+       if (expr->type == EXPR_PREOP)
+               return;
+
+       if (is_user_rl(expr)) {
+               if (expr->symbol && expr->symbol->ctype.as == 5) {
+                       name = expr_to_str(expr);
+                       sm_warning("potential tagged issue '%s'", name);
+                       free_string(name);
+               }
+       }
+}
+
+void check_tagged(int id)
+{
+       if (option_project != PROJ_KERNEL)
+               return;
+
+       add_hook(&untagged_check, EXPR_HOOK);
+}
 
This correctly identifies functions that have been annotated with the __untagged
annotation that use data from userspace. However, it's not actually that
useful...

For example, if I annotate the find_vma function, then it will flag this as a
potential tagged issue given that its called (from at least some contexts) with
userspace data. However find_vma is called all over the kernel, and so it's
difficult to figure out which caller called find_vma with user data. 

It's possible to use ./smatch_scripts/unlocked_paths.pl with a garbage lock and
'find_vma' target to look for all the callers where this is a leaf function, but
this doesn't track the call parameters to exclude paths where the leaf annotated
function contains user data.

I haven't fully understood var_user_rl - but it looks like functions such as
match_user_assign_function propogate the user_data tag on each run to update
state, and thus it's probably not easy to get the original source of user
data after is_user_rl determines its user. Is that correct?

I would be great if there was a way to obtain the function that provided the
original source of user data - then it would be possible to write a script that
shows the path (call chain) between the two functions - I hoped smatch_scripts/
call_tree.pl could do this but it doesn't seem to do anything for me.

Thanks,

Andrew Murray

> 
> regards,
> dan carpenter
> --------------
> 
> #include "check_debug.h"
> 
> int copy_from_user(void *dest, void *src, long size);
> 
> #define __s64 signed long long
> #define __u64 unsigned long long
> #define u64 unsigned long long
> #define __u8 unsigned char
> 
> /**
>  * sign_extend64 - sign extend a 64-bit value using specified bit as sign-bit
>  * @value: value to sign extend
>  * @index: 0 based bit index (0<=index<64) to sign bit
>  */
> static inline __s64 sign_extend64(__u64 value, int index)
> {
> 	__u8 shift = 63 - index;
> 	return (__s64)(value << shift) >> shift;
> }
> 
> #define untagged_addr(addr) ((__typeof__(addr))sign_extend64((u64)(addr), 55))
> 
> void func1(unsigned long x)
> {
> 	__smatch_user_rl(x);

It took more than a glance to realise what this was doing!

> }
> 
> void *p;
> int main(void)
> {
> 	unsigned long a;
> 
> 	copy_from_user(&a, p, sizeof(a));
> 	__smatch_user_rl(a);
> 	__smatch_user_rl(untagged_addr(a));
> 	func1(a);
> 	func1(untagged_addr(a));
> 
> 	return 0;
> }
> 

Thanks,

Andrew Murray
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.