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 <dmalc...@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)