[PATCH 1b/6] Add __attribute__((untrusted))

David Malcolm dmalcolm@redhat.com
Sat Nov 13 20:37:26 GMT 2021


This patch adds a new:

  __attribute__((untrusted))

for use by the C front-end, intended for use by the Linux kernel for
use with "__user", but which could be used by other operating system
kernels, and potentialy by other projects.

Known issues:
- at least one TODO in handle_untrusted_attribute
- should it be permitted to dereference an untrusted pointer?  The patch
  currently allows this

gcc/c-family/ChangeLog:
	* c-attribs.c (c_common_attribute_table): Add "untrusted".
	(build_untrusted_type): New.
	(handle_untrusted_attribute): New.
	* c-pretty-print.c (pp_c_cv_qualifiers): Handle
	TYPE_QUAL_UNTRUSTED.

gcc/c/ChangeLog:
	* c-typeck.c (convert_for_assignment): Complain if the trust
	levels vary when assigning a non-NULL pointer.

gcc/ChangeLog:
	* doc/extend.texi (Common Type Attributes): Add "untrusted".
	* print-tree.c (print_node): Handle TYPE_UNTRUSTED.
	* tree-core.h (enum cv_qualifier): Add TYPE_QUAL_UNTRUSTED.
	(struct tree_type_common): Assign one of the spare bits to a new
	"untrusted_flag".
	* tree.c (set_type_quals): Handle TYPE_QUAL_UNTRUSTED.
	* tree.h (TYPE_QUALS): Likewise.
	(TYPE_QUALS_NO_ADDR_SPACE): Likewise.
	(TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC): Likewise.

gcc/testsuite/ChangeLog:
	* c-c++-common/attr-untrusted-1.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/c-family/c-attribs.c                      |  59 +++++++
 gcc/c-family/c-pretty-print.c                 |   2 +
 gcc/c/c-typeck.c                              |  64 +++++++
 gcc/doc/extend.texi                           |  25 +++
 gcc/print-tree.c                              |   3 +
 gcc/testsuite/c-c++-common/attr-untrusted-1.c | 165 ++++++++++++++++++
 gcc/tree-core.h                               |   6 +-
 gcc/tree.c                                    |   1 +
 gcc/tree.h                                    |  11 +-
 9 files changed, 332 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/attr-untrusted-1.c

diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 007b928c54b..100c2dabab2 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -136,6 +136,7 @@ static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
 						 bool *);
 static tree handle_access_attribute (tree *, tree, tree, int, bool *);
 
+static tree handle_untrusted_attribute (tree *, tree, tree, int, bool *);
 static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
 static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *);
 static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *);
@@ -536,6 +537,8 @@ const struct attribute_spec c_common_attribute_table[] =
 			      handle_special_var_sec_attribute, attr_section_exclusions },
   { "access",		      1, 3, false, true, true, false,
 			      handle_access_attribute, NULL },
+  { "untrusted",	      0, 0, false,  true, false, true,
+			      handle_untrusted_attribute, NULL },
   /* Attributes used by Objective-C.  */
   { "NSObject",		      0, 0, true, false, false, false,
 			      handle_nsobject_attribute, NULL },
@@ -5224,6 +5227,62 @@ build_attr_access_from_parms (tree parms, bool skip_voidptr)
   return build_tree_list (name, attrargs);
 }
 
+/* Build (or reuse) a type based on BASE_TYPE, but with
+   TYPE_QUAL_UNTRUSTED.  */
+
+static tree
+build_untrusted_type (tree base_type)
+{
+  int base_type_quals = TYPE_QUALS (base_type);
+  return build_qualified_type (base_type,
+			       base_type_quals | TYPE_QUAL_UNTRUSTED);
+}
+
+/* Handle an "untrusted" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_untrusted_attribute (tree *node, tree ARG_UNUSED (name),
+			    tree ARG_UNUSED (args), int ARG_UNUSED (flags),
+			    bool *no_add_attrs)
+{
+  if (TREE_CODE (*node) == POINTER_TYPE)
+    {
+      tree base_type = TREE_TYPE (*node);
+      tree untrusted_base_type = build_untrusted_type (base_type);
+      *node = build_pointer_type (untrusted_base_type);
+      *no_add_attrs = true; /* OK */
+      return NULL_TREE;
+    }
+  else if (TREE_CODE (*node) == FUNCTION_TYPE)
+    {
+      tree return_type = TREE_TYPE (*node);
+      if (TREE_CODE (return_type) == POINTER_TYPE)
+	{
+	  tree base_type = TREE_TYPE (return_type);
+	  tree untrusted_base_type = build_untrusted_type (base_type);
+	  tree untrusted_return_type = build_pointer_type (untrusted_base_type);
+	  tree fn_type = build_function_type (untrusted_return_type,
+					      TYPE_ARG_TYPES (*node));
+	  *node = fn_type;
+	  *no_add_attrs = true; /* OK */
+	  return NULL_TREE;
+	}
+      else
+	{
+	  gcc_unreachable (); // TODO
+	}
+    }
+  else
+    {
+      tree base_type = *node;
+      tree untrusted_base_type = build_untrusted_type (base_type);
+      *node = untrusted_base_type;
+      *no_add_attrs = true; /* OK */
+      return NULL_TREE;
+    }
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index a987da46d6d..120e1e6d167 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -191,6 +191,8 @@ pp_c_cv_qualifiers (c_pretty_printer *pp, int qualifiers, bool func_type)
   if (qualifiers & TYPE_QUAL_RESTRICT)
     pp_c_ws_string (pp, (flag_isoc99 && !c_dialect_cxx ()
 			 ? "restrict" : "__restrict__"));
+  if (qualifiers & TYPE_QUAL_UNTRUSTED)
+    pp_c_ws_string (pp, "__attribute__((untrusted))");
 }
 
 /* Pretty-print T using the type-cast notation '( type-name )'.  */
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 782414f8c8c..44de82b99ba 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -7284,6 +7284,70 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	  return error_mark_node;
 	}
 
+      /* Untrusted vs trusted pointers, but allowing NULL to be used
+	 for everything.  */
+      if (TYPE_UNTRUSTED (ttl) != TYPE_UNTRUSTED (ttr)
+	  && !null_pointer_constant_p (rhs))
+	{
+	  auto_diagnostic_group d;
+	  bool diagnosed = true;
+	  switch (errtype)
+	    {
+	    case ic_argpass:
+	      {
+		const char msg[] = G_("passing argument %d of %qE from "
+				      "pointer with different trust level");
+		if (warnopt)
+		  diagnosed
+		    = warning_at (expr_loc, warnopt, msg, parmnum, rname);
+		else
+		  error_at (expr_loc, msg, parmnum, rname);
+	      break;
+	      }
+	    case ic_assign:
+	      {
+		const char msg[] = G_("assignment from pointer with "
+				      "different trust level");
+		if (warnopt)
+		  warning_at (location, warnopt, msg);
+		else
+		  error_at (location, msg);
+		break;
+	      }
+	    case ic_init:
+	      {
+		const char msg[] = G_("initialization from pointer with "
+				      "different trust level");
+		if (warnopt)
+		  warning_at (location, warnopt, msg);
+		else
+		  error_at (location, msg);
+		break;
+	      }
+	    case ic_return:
+	      {
+		const char msg[] = G_("return from pointer with "
+				      "different trust level");
+		if (warnopt)
+		  warning_at (location, warnopt, msg);
+		else
+		  error_at (location, msg);
+		break;
+	      }
+	    default:
+	      gcc_unreachable ();
+	    }
+	  if (diagnosed)
+	    {
+	      if (errtype == ic_argpass)
+		inform_for_arg (fundecl, expr_loc, parmnum, type, rhstype);
+	      else
+		inform (location, "expected %qT but pointer is of type %qT",
+			type, rhstype);
+	    }
+	  return error_mark_node;
+	}
+
       /* Check if the right-hand side has a format attribute but the
 	 left-hand side doesn't.  */
       if (warn_suggest_attribute_format
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 6e6c580e329..e9f47519df2 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8770,6 +8770,31 @@ pid_t wait (wait_status_ptr_t p)
 @}
 @end smallexample
 
+@item untrusted
+@cindex @code{untrusted} type attribute
+Types marked with this attribute are treated as being ``untrusted'' -
+values should be treated as under attacker control.
+
+The C front end will issue an error diagnostic on attempts to assign
+pointer values between untrusted and trusted pointer types without
+an explicit cast.
+
+For example, when implementing an operating system kernel, one
+might write
+
+@smallexample
+#define __kernel
+#define __user    __attribute__ ((untrusted))
+void __kernel *p_kernel;
+void __user *p_user;
+
+/* With the above, the following assignment should be diagnosed as an error.  */
+p_user = p_kernel;
+@end smallexample
+
+The NULL pointer is treated as being usable with both trusted and
+untrusted pointers.
+
 @item unused
 @cindex @code{unused} type attribute
 When attached to a type (including a @code{union} or a @code{struct}),
diff --git a/gcc/print-tree.c b/gcc/print-tree.c
index d1fbd044c27..e5123807521 100644
--- a/gcc/print-tree.c
+++ b/gcc/print-tree.c
@@ -640,6 +640,9 @@ print_node (FILE *file, const char *prefix, tree node, int indent,
       if (TYPE_RESTRICT (node))
 	fputs (" restrict", file);
 
+      if (TYPE_UNTRUSTED (node))
+	fputs (" untrusted", file);
+
       if (TYPE_LANG_FLAG_0 (node))
 	fputs (" type_0", file);
       if (TYPE_LANG_FLAG_1 (node))
diff --git a/gcc/testsuite/c-c++-common/attr-untrusted-1.c b/gcc/testsuite/c-c++-common/attr-untrusted-1.c
new file mode 100644
index 00000000000..84a217fc59f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-untrusted-1.c
@@ -0,0 +1,165 @@
+#define __kernel
+#define __user __attribute__((untrusted))
+#define __iomem
+#define __percpu
+#define __rcu
+
+void *p;
+void __kernel *p_kernel;
+void __user *p_user;
+void __iomem *p_iomem;
+void __percpu *p_percpu;
+void __rcu *p_rcu;
+
+#define NULL ((void *)0)
+
+extern void accepts_p (void *); /* { dg-message "24: expected 'void \\*' but argument is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } } */
+/* { dg-message "24:  initializing argument 1 of 'void accepts_p\\(void\\*\\)'" "" { target c++ } .-1 } */
+extern void accepts_p_kernel (void __kernel *);
+extern void accepts_p_user (void __user *);
+
+void test_argpass_to_p (void)
+{
+  accepts_p (p);
+  accepts_p (p_kernel);
+  accepts_p (p_user); /* { dg-error "passing argument 1 of 'accepts_p' from pointer with different trust level" "" { target c } } */
+  /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-1 } */
+}
+
+void test_init_p (void)
+{
+  void *local_p_1 = p;
+  void *local_p_2 = p_kernel;
+  void *local_p_3 = p_user; /* { dg-error "initialization from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+  /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+}
+
+void test_init_p_kernel (void)
+{
+  void __kernel *local_p_1 = p;
+  void __kernel *local_p_2 = p_kernel;
+  void __kernel *local_p_3 = p_user; /* { dg-error "initialization from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+  /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+}
+
+void test_init_p_user (void)
+{
+  void __user *local_p_1 = p; /* { dg-error "initialization from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+  void __user *local_p_2 = p_kernel; /* { dg-error "initialization from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+  void __user *local_p_3 = p_user;
+  void __user *local_p_4 = NULL;
+}
+
+void test_assign_to_p (void)
+{
+  p = p;
+  p = p_kernel;
+  p = p_user; /* { dg-error "assignment from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+  /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+  // etc
+}
+
+void test_assign_to_p_kernel (void)
+{
+  p_kernel = p;
+  p_kernel = p_kernel;
+  p_kernel = p_user; /* { dg-error "assignment from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+  /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+  // etc
+}
+
+void test_assign_to_p_user (void)
+{
+  p_user = p;  /* { dg-error "assignment from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+  p_user = p_kernel;  /* { dg-error "assignment from pointer with different trust level" "" { target c } } */
+  /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+  p_user = p_user;
+  p_user = NULL;
+  // etc
+}
+
+void *test_return_p (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p;
+    case 1:
+      return p_kernel;
+    case 2:
+      return p_user; /* { dg-error "return from pointer with different trust level" "" { target c } } */
+      /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+      /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+    }
+}
+
+void __kernel *test_return_p_kernel (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p;
+    case 1:
+      return p_kernel;
+    case 2:
+      return p_user; /* { dg-error "return from pointer with different trust level" "" { target c } } */
+      /* { dg-message "expected 'void \\*' but pointer is of type '__attribute__\\(\\(untrusted\\)\\) void \\*'" "" { target c } .-1 } */
+      /* { dg-error "invalid conversion from '__attribute__\\(\\(untrusted\\)\\) void\\*' to 'void\\*'" "" { target c++ } .-2 } */
+    }
+}
+
+void __user *
+test_return_p_user (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p; /* { dg-error "return from pointer with different trust level" "" { target c } } */
+      /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+    case 1:
+      return p_kernel; /* { dg-error "return from pointer with different trust level" "" { target c } } */
+      /* { dg-message "expected '__attribute__\\(\\(untrusted\\)\\) void \\*' but pointer is of type 'void \\*'" "" { target c } .-1 } */
+    case 2:
+      return p_user;
+    case 3:
+      return NULL;
+    }
+}
+
+void test_cast_k_to_u (void)
+{
+  p_user = (void __user *)p_kernel;
+}
+
+void test_cast_u_to_k (void)
+{
+  p_kernel = (void __kernel *)p_user;
+}
+
+int test_deref_read (int __user *p)
+{
+  return *p; // FIXME: should this be allowed directly?
+}
+
+void test_deref_write (int __user *p, int i)
+{
+  *p = i; // FIXME: should this be allowed directly?
+}
+
+typedef struct foo { int i; } __user *foo_ptr_t;
+
+void __user *
+test_pass_through (void __user *ptr)
+{
+  return ptr;
+}
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 8ab119dc9a2..35a7f50c06c 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -604,7 +604,8 @@ enum cv_qualifier {
   TYPE_QUAL_CONST    = 0x1,
   TYPE_QUAL_VOLATILE = 0x2,
   TYPE_QUAL_RESTRICT = 0x4,
-  TYPE_QUAL_ATOMIC   = 0x8
+  TYPE_QUAL_ATOMIC   = 0x8,
+  TYPE_QUAL_UNTRUSTED = 0x10
 };
 
 /* Standard named or nameless data types of the C compiler.  */
@@ -1684,7 +1685,8 @@ struct GTY(()) tree_type_common {
   unsigned typeless_storage : 1;
   unsigned empty_flag : 1;
   unsigned indivisible_p : 1;
-  unsigned spare : 16;
+  unsigned untrusted_flag : 1;
+  unsigned spare : 15;
 
   alias_set_type alias_set;
   tree pointer_to;
diff --git a/gcc/tree.c b/gcc/tree.c
index 845228a055b..3600639d985 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -5379,6 +5379,7 @@ set_type_quals (tree type, int type_quals)
   TYPE_VOLATILE (type) = (type_quals & TYPE_QUAL_VOLATILE) != 0;
   TYPE_RESTRICT (type) = (type_quals & TYPE_QUAL_RESTRICT) != 0;
   TYPE_ATOMIC (type) = (type_quals & TYPE_QUAL_ATOMIC) != 0;
+  TYPE_UNTRUSTED (type) = (type_quals & TYPE_QUAL_UNTRUSTED) != 0;
   TYPE_ADDR_SPACE (type) = DECODE_QUAL_ADDR_SPACE (type_quals);
 }
 
diff --git a/gcc/tree.h b/gcc/tree.h
index f62c00bc870..caab575b210 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -2197,6 +2197,10 @@ extern tree vector_element_bits_tree (const_tree);
    the term.  */
 #define TYPE_RESTRICT(NODE) (TYPE_CHECK (NODE)->type_common.restrict_flag)
 
+/* Nonzero in a type considered "untrusted" - values should be treated as
+   under attacker control.  */
+#define TYPE_UNTRUSTED(NODE) (TYPE_CHECK (NODE)->type_common.untrusted_flag)
+
 /* If nonzero, type's name shouldn't be emitted into debug info.  */
 #define TYPE_NAMELESS(NODE) (TYPE_CHECK (NODE)->base.u.bits.nameless_flag)
 
@@ -2221,6 +2225,7 @@ extern tree vector_element_bits_tree (const_tree);
 	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
 	  | (TYPE_ATOMIC (NODE) * TYPE_QUAL_ATOMIC)		\
 	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)		\
+	  | (TYPE_UNTRUSTED (NODE) * TYPE_QUAL_UNTRUSTED)	\
 	  | (ENCODE_QUAL_ADDR_SPACE (TYPE_ADDR_SPACE (NODE)))))
 
 /* The same as TYPE_QUALS without the address space qualifications.  */
@@ -2228,14 +2233,16 @@ extern tree vector_element_bits_tree (const_tree);
   ((int) ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
 	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
 	  | (TYPE_ATOMIC (NODE) * TYPE_QUAL_ATOMIC)		\
-	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
+	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)		\
+	  | (TYPE_UNTRUSTED (NODE) * TYPE_QUAL_UNTRUSTED)))
 
 /* The same as TYPE_QUALS without the address space and atomic 
    qualifications.  */
 #define TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC(NODE)		\
   ((int) ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
 	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
-	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
+	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)		\
+	  | (TYPE_UNTRUSTED (NODE) * TYPE_QUAL_UNTRUSTED)))
 
 /* These flags are available for each language front end to use internally.  */
 #define TYPE_LANG_FLAG_0(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_0)
-- 
2.26.3



More information about the Gcc-patches mailing list