Add checks for invalid read sizes from a binary policy to guard
allocations.
The common and class permission counts needs to be limited more strict
otherwise a too high count of common or class permissions can lead to
permission values with a too high value, which can lead to overflows
in shift operations.
In the fuzzer build the value will also be bounded to avoid oom reports.
==29857== ERROR: libFuzzer: out-of-memory (malloc(17179868160))
To change the out-of-memory limit use -rss_limit_mb=<N>
#0 0x52dc61 in __sanitizer_print_stack_trace (./out/binpolicy-fuzzer+0x52dc61)
#1 0x475618 in fuzzer::PrintStackTrace() fuzzer.o
#2 0x458855 in fuzzer::Fuzzer::HandleMalloc(unsigned long) fuzzer.o
#3 0x45876a in fuzzer::MallocHook(void const volatile*, unsigned long) fuzzer.o
#4 0x534557 in __sanitizer::RunMallocHooks(void const*, unsigned long) (./out/binpolicy-fuzzer+0x534557)
#5 0x4aa7d7 in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) (./out/binpolicy-fuzzer+0x4aa7d7)
#6 0x4aa143 in __asan::asan_malloc(unsigned long, __sanitizer::BufferedStackTrace*) (./out/binpolicy-fuzzer+0x4aa143)
#7 0x5259cb in malloc (./out/binpolicy-fuzzer+0x5259cb)
#8 0x580b5d in mallocarray ./libsepol/src/./private.h:93:9
#9 0x57c2ed in scope_read ./libsepol/src/policydb.c:4120:7
#10 0x576b0d in policydb_read ./libsepol/src/policydb.c:4462:9
#11 0x55a214 in LLVMFuzzerTestOneInput ./libsepol/fuzz/binpolicy-fuzzer.c:26:6
#12 0x45aed3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) fuzzer.o
#13 0x446a12 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) fuzzer.o
#14 0x44c93b in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) fuzzer.o
#15 0x475dd2 in main (./out/binpolicy-fuzzer+0x475dd2)
#16 0x7ffad6e107ec in __libc_start_main csu/../csu/libc-start.c:332:16
#17 0x423689 in _start (./out/binpolicy-fuzzer+0x423689)
==19462== ERROR: libFuzzer: out-of-memory (malloc(18253611008))
To change the out-of-memory limit use -rss_limit_mb=<N>
#0 0x52dc61 in __sanitizer_print_stack_trace (./out/binpolicy-fuzzer+0x52dc61)
#1 0x475618 in fuzzer::PrintStackTrace() fuzzer.o
#2 0x458855 in fuzzer::Fuzzer::HandleMalloc(unsigned long) fuzzer.o
#3 0x45876a in fuzzer::MallocHook(void const volatile*, unsigned long) fuzzer.o
#4 0x534557 in __sanitizer::RunMallocHooks(void const*, unsigned long) (./out/binpolicy-fuzzer+0x534557)
#5 0x4aa7d7 in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) (./out/binpolicy-fuzzer+0x4aa7d7)
#6 0x4aa999 in __asan::asan_calloc(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*) (./out/binpolicy-fuzzer+0x4aa999)
#7 0x525b63 in __interceptor_calloc (./out/binpolicy-fuzzer+0x525b63)
#8 0x570938 in policydb_index_others ./libsepol/src/policydb.c:1245:6
#9 0x5771f3 in policydb_read ./src/policydb.c:4481:6
#10 0x55a214 in LLVMFuzzerTestOneInput ./libsepol/fuzz/binpolicy-fuzzer.c:26:6
#11 0x45aed3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) fuzzer.o
#12 0x446a12 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) fuzzer.o
#13 0x44c93b in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) fuzzer.o
#14 0x475dd2 in main (./out/binpolicy-fuzzer+0x475dd2)
#15 0x7f4d933157ec in __libc_start_main csu/../csu/libc-start.c:332:16
#16 0x423689 in _start (./out/binpolicy-fuzzer+0x423689)
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Use a wrapper to guard `realloc(p, a * b)` type allocations, to detect
multiplication overflows, which result in too few memory being
allocated.
Use a custom implementation if the used C library does not offer one.
Also use temporary variables for realloc(3) results in add_i_to_a() and
fp_to_buffer().
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Use a wrapper to guard `malloc(a * b)` type allocations, to detect
multiplication overflows, which result in too few memory being
allocated.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Use the internal logging framework instead of directly writing to
stdout as it might be undesired to do so within a library.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Use the internal logging framework instead of directly writing to
stdout as it might be undesired to do so within a library.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Limit the maximum length of read sizes, like string length of module
version and name or keys and number of symtab entries. This avoids the
fuzzer to report oom events for huge allocations (it also improves the
number of executions per seconds of the fuzzer).
This change only affects the fuzzer build.
==15211== ERROR: libFuzzer: out-of-memory (malloc(3115956666))
To change the out-of-memory limit use -rss_limit_mb=<N>
#0 0x52dc61 in __sanitizer_print_stack_trace (./out/binpolicy-fuzzer+0x52dc61)
#1 0x475618 in fuzzer::PrintStackTrace() fuzzer.o
#2 0x458855 in fuzzer::Fuzzer::HandleMalloc(unsigned long) fuzzer.o
#3 0x45876a in fuzzer::MallocHook(void const volatile*, unsigned long) fuzzer.o
#4 0x534557 in __sanitizer::RunMallocHooks(void const*, unsigned long) (./out/binpolicy-fuzzer+0x534557)
#5 0x4aa7d7 in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) (./out/binpolicy-fuzzer+0x4aa7d7)
#6 0x4aa143 in __asan::asan_malloc(unsigned long, __sanitizer::BufferedStackTrace*) (./out/binpolicy-fuzzer+0x4aa143)
#7 0x5259cb in malloc (./out/binpolicy-fuzzer+0x5259cb)
#8 0x59d307 in str_read ./libsepol/src/services.c:1746:8
#9 0x585b97 in perm_read ./libsepol/src/policydb.c:2063:5
#10 0x581f8a in common_read ./libsepol/src/policydb.c:2119:7
#11 0x576681 in policydb_read ./libsepol/src/policydb.c:4417:8
#12 0x55a214 in LLVMFuzzerTestOneInput ./libsepol/fuzz/binpolicy-fuzzer.c:26:6
#13 0x45aed3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) fuzzer.o
#14 0x446a12 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) fuzzer.o
#15 0x44c93b in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) fuzzer.o
#16 0x475dd2 in main (./out/binpolicy-fuzzer+0x475dd2)
#17 0x7fe1ec88a7ec in __libc_start_main csu/../csu/libc-start.c:332:16
#18 0x423689 in _start (./out/binpolicy-fuzzer+0x423689)
==12683== ERROR: libFuzzer: out-of-memory (malloc(2526451450))
To change the out-of-memory limit use -rss_limit_mb=<N>
#0 0x52dc61 in __sanitizer_print_stack_trace (./out/binpolicy-fuzzer+0x52dc61)
#1 0x475618 in fuzzer::PrintStackTrace() fuzzer.o
#2 0x458855 in fuzzer::Fuzzer::HandleMalloc(unsigned long) fuzzer.o
#3 0x45876a in fuzzer::MallocHook(void const volatile*, unsigned long) fuzzer.o
#4 0x534557 in __sanitizer::RunMallocHooks(void const*, unsigned long) (./out/binpolicy-fuzzer+0x534557)
#5 0x4aa7d7 in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) (./out/binpolicy-fuzzer+0x4aa7d7)
#6 0x4aa143 in __asan::asan_malloc(unsigned long, __sanitizer::BufferedStackTrace*) (./out/binpolicy-fuzzer+0x4aa143)
#7 0x5259cb in malloc (./out/binpolicy-fuzzer+0x5259cb)
#8 0x575f8a in policydb_read ./libsepol/src/policydb.c:4356:18
#9 0x55a214 in LLVMFuzzerTestOneInput ./libsepol/fuzz/binpolicy-fuzzer.c:26:6
#10 0x45aed3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) fuzzer.o
#11 0x446a12 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) fuzzer.o
#12 0x44c93b in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) fuzzer.o
#13 0x475dd2 in main (./out/binpolicy-fuzzer+0x475dd2)
#14 0x7fa737b377ec in __libc_start_main csu/../csu/libc-start.c:332:16
#15 0x423689 in _start (./out/binpolicy-fuzzer+0x423689)
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Introduce a libfuzz[1] based fuzzer testing the parsing of a binary
policy.
Build the fuzzer in the oss-fuzz script.
[1]: https://llvm.org/docs/LibFuzzer.html
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Do not output CIL log messages while fuzzing, since their amount are
huge, e.g. for neverallow or typebounds violations.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Accept IPv4 addresses embedded in IPv6, like `::ffff:127.0.0.1`.
This allows using those in nodecon statements leading to fine grained
access control:
type=AVC msg=audit(11/29/21 20:27:44.437:419) : avc: granted { node_bind } for pid=27500 comm=intercept saddr=::ffff:127.0.0.1 src=46293 scontext=xuser_u:xuser_r:xuser_t:s0 tcontext=system_u:object_r:lo_node_t:s0 tclass=tcp_socket
This does effect policies in the traditional language due to CIL usage
in semodule(8).
Also print on conversion failures the address in question.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
With an optional file type being added to CIL genfscon rules, it
should be used when writing out a kernel policy or module to CIL
when a genfscon rule should only apply to a single security class.
Signed-off-by: James Carter <jwcart2@gmail.com>
The optional specification of a file type for a genfscon rule to
make it apply only to a specific security class is allowed by
checkpolicy and checkmodule and should be allowed for CIL policies
as well.
Allow an optional file type to be specified for a genfscon rule.
The new syntax:
(genfscon FSNAME PATH [FILE_TYPE] CONTEXT)
FSNAME - The name of the supported filesystem
PATH - If FSNAME is proc then this is the partial path,
othewise this must be "/".
FILE_TYPE - A single keyword representing the file type.
file type security class
any Same as not specifying a file type
file file
dir dir
char chr_file
block blk_file
socket sock_file
pipe fifo_file
symlink lnk_file
CONTEXT - Either a previously declared security context identifier
or an anonymous security context.
Signed-off-by: James Carter <jwcart2@gmail.com>
Prepare for the addition of an optional file type in genfscon rules
by refactoring filecon file type handling.
Make the "any" file type be the first value in enum cil_filecon_types
because it will be the most common file type.
Signed-off-by: James Carter <jwcart2@gmail.com>
Although rarely used, genfscon rules support the specification of a
file type just like the rules in a file context file. The file type
is used to make the genfscon rule apply only for a specific security
class. Currently, when writing out a policy.conf file from a kernel
policy, it is assumed that every genfscon rule applies to all security
classes and no file type will be added to the genfscon rule.
Write out the appropriate file type if the genfscon rule is only for
a specific security class (file, dir, blk_file, chr_file, fifo_file,
lnk_file, or sock_file).
Signed-off-by: James Carter <jwcart2@gmail.com>
Use string literals as format strings so that compilers can validate the
count and types of the inherent arguments.
kernel_to_cil.c: In function ‘class_constraint_rules_to_strs’:
kernel_to_cil.c:301:17: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
301 | rc = strs_create_and_add(strs, format_str, 3, classkey, perms+1, expr);
| ^~
kernel_to_cil.c: In function ‘class_validatetrans_rules_to_strs’:
kernel_to_cil.c:341:17: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
341 | rc = strs_create_and_add(strs, format_str, 2, classkey, expr);
| ^~
kernel_to_cil.c: In function ‘cats_ebitmap_to_str’:
kernel_to_cil.c:1068:40: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
1068 | val_to_name[start], val_to_name[i]);
| ^~~~~~~~~~~
kernel_to_conf.c: In function ‘class_constraint_rules_to_strs’:
kernel_to_conf.c:301:42: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
301 | flavor, classkey, perms+1, expr);
| ^~~~~~
kernel_to_conf.c: In function ‘cats_ebitmap_to_str’:
kernel_to_conf.c:1059:40: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
1059 | val_to_name[start], sep, val_to_name[i]);
| ^~~~~~~~~~~
kernel_to_conf.c:1062:25: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
1062 | len = snprintf(p, remaining, fmt, val_to_name[start]);
| ^~~
module_to_cil.c: In function ‘cond_expr_to_cil’:
module_to_cil.c:1340:25: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
1340 | rlen = snprintf(new_val, len, fmt_str, op, val1, val2);
| ^~~~
module_to_cil.c: In function ‘constraint_expr_to_string’:
module_to_cil.c:1881:25: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
1881 | rlen = snprintf(new_val, len, fmt_str, op, val1, val2);
| ^~~~
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
memcpy(3) might be annotated with the function attribute nonnull and
UBSan then complains:
module.c:296:3: runtime error: null pointer passed as argument 2, which is declared to never be null
#0 0x7f2468efa5b3 in link_netfilter_contexts ./libsepol/src/module.c:296
#1 0x7f2468efa5b3 in sepol_link_packages ./libsepol/src/module.c:337
#2 0x562331e9e123 in main ./semodule-utils/semodule_link/semodule_link.c:145
#3 0x7f2467e247ec in __libc_start_main ../csu/libc-start.c:332
#4 0x562331e9d2a9 in _start (./destdir/usr/bin/semodule_link+0x32a9)
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
For the first iteration `mod->perm_map[sclassi]` is NULL, thus do not
use it as source of a memcpy(3), even with a size of 0. memcpy(3) might
be annotated with the function attribute nonnull and UBSan then
complains:
link.c:193:3: runtime error: null pointer passed as argument 2, which is declared to never be null
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
An expression of the form "1 << x" is undefined if x == 31 because
the "1" is an int and cannot be left shifted by 31.
Instead, use "UINT32_C(1) << x" which will be an unsigned int of
at least 32 bits.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
An expression of the form "1 << x" is undefined if x == 31 because
the "1" is an int and cannot be left shifted by 31.
Instead, use "UINT32_C(1) << x" which will be an unsigned int of
at least 32 bits.
Signed-off-by: James Carter <jwcart2@gmail.com>
Since only tunableifs need to be resolved in a macro before the macro
is copied for each call, macros were being skipped after resolving
tunableifs. Statments not allowed to be in macros would be found during
the pass that resolved tunableifs. Unfortunately, in-statments are
resolved after tunableifs and they can be used to add statements to
macros that are not allowed.
Instead, do not skip macros until after the pass that resolves in-
statements that are to be resolved after block inheritance. This
allows blocks, blockinherits, blockabstracts, and macros that were
added by an in-statement to be found and an error reported.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Type bounds are checked when creating the CIL binary using libsepol
functions on the binary policy db. The bad rule is reported and, to
provide better error reporting, a search is made for matching rules
in the CIL policy. These matching rules as well as their parents are
written out with their locations to make it easier to find the rules
that violate the type bounds.
It is possible to craft CIL policies where there are many rules
that violate a bounds check each with many matching rules as well.
This can make the error messages very difficult to deal with. For
example, if there are 100 rules in the binary policy db that violate
a type bounds and each of these rules has 100 matches, then 10,000
matching rules along with their parents will be written out as part
of the error message.
Limit the error reporting to two rules for each type bounds violation
along with two matches for each of those rules.
This problem was found with the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Add an intermediate cast to uintptr_t to silence the clang specific
warning about casting a void pointer to an enum.
../cil/src/cil_verify.c:1749:28: error: cast to smaller integer type 'enum cil_flavor' from 'void *' [-Werror,-Wvoid-pointer-to-enum-cast]
enum cil_flavor op = (enum cil_flavor)i->data;
^~~~~~~~~~~~~~~~~~~~~~~~
Similar to 32f8ed3d6b.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
GCC reports a NULL dereference of the return value of stack_peek(). This
function explicitly returns NULL in case of 'stack->pos == -1'.
Error out on NULL returned.
module_to_cil.c: In function ‘block_to_cil’:
module_to_cil.c:3357:55: error: potential null pointer dereference [-Werror=null-dereference]
3357 | struct list *alias_list = typealias_lists[decl->decl_id];
| ~~~~^~~~~~~~~
There are more occurrences of unconditionally dereferencing the return
value of stack_peek(), but the callers should ensure a valid stack, so
just silence this single warning.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
The function hashtab_insert takes the type hashtab_datum_t (alias void*)
as third argument. Do not cast to hashtab_datum_t* alias void**. The
casts could be dropped, as explicit casting to void* is unnecessary, but
to fit the overall style of this file keep the casts.
expand.c:246:41: error: cast from 'perm_datum_t *' (aka 'struct perm_datum *') to 'hashtab_datum_t *' (aka 'void **') increases required alignment from 4 to 8 [-Werror,-Wcast-align]
ret = hashtab_insert(s->table, new_id, (hashtab_datum_t *) new_perm);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Mark pointers to nodes of const ebitmaps also const. C does not enforce
a transitive const-ness, but it clarifies the intent and improves
maintainability.
Follow-up of 390ec54d27
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
When checking for circular class permission declarations and a class
mapping is encountered, the class permissions for each map permission
must be checked. An assumption was made that there were no operators
in the class permissions. An operator in the class permissions would
cause a segfault.
Example causing segault:
(classmap cm1 (mp1))
(classmapping cm1 mp1 (CLASS (PERM)))
(classpermission cp1)
(classpermissionset cp1 (cm1 (all)))
For map class permissions, check each item in the permission list to
see if it is an operator. If it is not, then verify the class
permissions associated with the map permission. If it is an operator
and the operator is "all", then create a list of all permissions for
that map class and verify the class permissions associated with each
map permission. If it is a different operator, then it can be skipped.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
When compiling CIL policy using secilc's "-m" option (which allows
duplicate declarations for types and type attributes), a segfault
will occur if the type or type attribute being copied has already
been declared. This is because a search of the symbol table is made
during the copy and the original datum will be used if one is found.
The original datum will be considered a duplicate when an attempt is
made to add it to the symbol table. The original datum, which is still
in use, will then be destroyed and a segfault will follow soon after
that.
Instead, always create a new datum. When it is added the new datum
will be destroyed if it is a duplicate and duplicate declarations
are allowed.
Signed-off-by: James Carter <jwcart2@gmail.com>
Previously, Android used its own cil_write_ast function to output the
resulting AST. libsepol now defines a similar function named
cil_write_build_ast. The new function differs slightly in behaviour:
* It will output "source information" nodes in the resulting CIL. When
loading, it is expected that each source information line (e.g.,
`;;* lms 100 file.cil`) will be matched with a terminating entry (e.g.,
`;;* lme`). If not, the loading will fail. Because we split and merge
policy files in AOSP, explicitly ignore these lines when writing the
AST.
* genfscon paths are now quoted following 644c5bb.
* An extra superfluous set of parentheses was previously added for some
operators (e.g., "range" "and" or "not").
For typeattributes, cil_write_build_ast uses the `fqn` field and not
`name`. Ensure the nodes are correctly populated.
Bug: 190808996
Test: Build aosp_bramble-userdebug and manually compare the generated
/{system,vendor,product}/etc/selinux* files with their previous
versions. The differences are due to the new behaviours described
above.
Test: Force a recompilation of the policy on device, the new policy is
correctly loaded.
Change-Id: I088d1e94ca07cfbd0b6c604f1f82464b3537c392
Found while running the checkpolicy/test/dispol binary.
Direct leak of 24 byte(s) in 1 object(s) allocated from:
#0 0x49bacd in __interceptor_malloc (./checkpolicy/test/dispol+0x49bacd)
#1 0x5551e1 in ebitmap_set_bit ./libsepol/src/ebitmap.c:326:27
#2 0x517873 in create_gap_ebitmap ./libsepol/src/policydb_validate.c:23:8
#3 0x517873 in validate_init ./libsepol/src/policydb_validate.c:34:6
#4 0x50fa47 in validate_array_init ./libsepol/src/policydb_validate.c:44:6
#5 0x50fa47 in validate_policydb ./libsepol/src/policydb_validate.c:732:6
#6 0x4f22df in policydb_read ./libsepol/src/policydb.c:4538:6
#7 0x4cddb3 in main ./checkpolicy/test/dispol.c:437:8
#8 0x7f5980e47e49 in __libc_start_main csu/../csu/libc-start.c:314:16
Indirect leak of 48 byte(s) in 2 object(s) allocated from:
#0 0x49bacd in __interceptor_malloc (./checkpolicy/test/dispol+0x49bacd)
#1 0x5551e1 in ebitmap_set_bit ./libsepol/src/ebitmap.c:326:27
#2 0x517873 in create_gap_ebitmap ./libsepol/src/policydb_validate.c:23:8
#3 0x517873 in validate_init ./libsepol/src/policydb_validate.c:34:6
#4 0x50fa47 in validate_array_init ./libsepol/src/policydb_validate.c:44:6
#5 0x50fa47 in validate_policydb ./libsepol/src/policydb_validate.c:732:6
#6 0x4f22df in policydb_read ./libsepol/src/policydb.c:4538:6
#7 0x4cddb3 in main ./checkpolicy/test/dispol.c:437:8
#8 0x7f5980e47e49 in __libc_start_main csu/../csu/libc-start.c:314:16
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Avoid implicit conversions from signed to unsigned values, found by
UB sanitizers, by using unsigned values in the first place.
util.c:95:15: runtime error: left shift of 1 by 31 places cannot be represented in type 'int'
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Duplicate declarations are allowed for type, typeattribute, and
optional statements. When an allowed duplicate declaration is found,
the duplicate datum is free'd in cil_add_decl_to_symtab() and SEPOL_OK
is returned. This works for all the rules where a duplicate declaration
is allowed, but it confuses scanning tools.
When cil_add_decl_to_symtab() finds an allowed duplicate declaration,
return SEPOL_EEXIST and free the duplicate datum in the original
calling function.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Petr Lautrbach <plautrba@redhat.com>
libsepol/cil/src/cil_binary.c:4823: alloc_arg: "bounds_check_type" allocates memory that is stored into "bad".
libsepol/cil/src/cil_binary.c:4840: var_assign: Assigning: "cur" = "bad".
libsepol/cil/src/cil_binary.c:4844: noescape: Resource "cur" is not freed or pointed-to in "cil_avrule_from_sepol".
libsepol/cil/src/cil_binary.c:4847: leaked_storage: Variable "cur" going out of scope leaks the storage it points to.
libsepol/cil/src/cil_binary.c:4847: leaked_storage: Variable "bad" going out of scope leaks the storage it points to.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
Acked-by: James Carter <jwcart2@gmail.com>
A line mark functions like an open parenthesis, so the number of
active line marks should be limited like the number of open
parenthesis.
This issue was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Fixes:
Error: RESOURCE_LEAK (CWE-772): [#def5]
libsepol/src/kernel_to_cil.c:2380: alloc_arg: "strs_init" allocates memory that is stored into "strs".
libsepol/src/kernel_to_cil.c:2386: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_cil.c:2386: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_cil.c:2386: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_cil.c:2507: leaked_storage: Variable "strs" going out of scope leaks the storage it points to.
libsepol/src/kernel_to_conf.c:2315: alloc_arg: "strs_init" allocates memory that is stored into "strs".
libsepol/src/kernel_to_conf.c:2321: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_conf.c:2321: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_conf.c:2321: noescape: Resource "strs" is not freed or pointed-to in "strs_add".
libsepol/src/kernel_to_conf.c:2385: leaked_storage: Variable "strs" going out of scope leaks the storage it points to.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
Acked-by: James Carter <jwcart2@gmail.com>
The function __cil_verify_syntax() is used to check the syntax of
CIL rules (and a few other common things like contexts and class
permissions). It does not correctly check the syntax combination
"CIL_SYN_STRING | CIL_SYN_N_LISTS, CIL_SYN_N_LISTS | CIL_SYN_END".
This should mean either a string followed by any number of lists
or any number of lists followed by the end of the rule. Instead,
while allowing the correct syntax, it allows any number of lists
followed by a string followed by any number of more lists followed
by the end of the rule and, also, any number of lists followed by a
string followed by the end of the rule.
Refactor the function to make it clearer to follow and so that once
checking begins for CIL_SYN_N_LISTS or CIL_SYN_N_STRINGS, then only
strings or lists are allowed until the end of the rule is found. In
addition, always check for CIL_SYN_END at the end.
Signed-off-by: James Carter <jwcart2@gmail.com>
Since the value passed into __cil_verify_syntax() as the len
parameter is always calculated from sizeof(syntax)/sizeof(*syntax),
use size_t for the calculated value in the calling function and for
the len parameter. In __cil_verify_syntax(), the variable i is only
compared to len, so make that size_t as well.
Signed-off-by: James Carter <jwcart2@gmail.com>
For every call to cil_fill_classperms_list(), the syntax of the
whole rule, including the class permissions, has already been
checked. There is no reason to check it again. Also, because the
class permissions appear in the middle of some rules, like
constraints, the syntax array does not end with CIL_SYN_END. This
is the only case where the syntax array does not end with CIL_SYN_END.
This prevents __cil_verify_syntax() from requiring that the syntax
array ends with CIL_SYN_END.
Remove the redundant syntax checking in cil_fill_classperms_list().
Signed-off-by: James Carter <jwcart2@gmail.com>
CIL's in-statement is resolved before block inheritance. This has
the advantage of allowing an in-statement to add rules to a base
block (say for a new permission) and having those rules also be
added everywhere that base block is inherited. But the disadvantage
of this behavior is that it is not possible to use an in-statement
on a block that is inherited for the simple reason that that block
does not exist when the in-statment is resolved.
Change the syntax of the in-statement to allow specifying whether
the rules should be added before or after inheritance. If neither
is specified, then the behavior remains the same. All current
in-statements will work as before.
Either the old syntax
(in container_id
cil_statement
...
)
or the new syntax
(in before|after container_id
cil_statement
...
)
may be used for in-statements. But only "(in after ..." will have
the new behavior. Using "(in before ..." will give the same
behavior as before.
Macro Example
;
(block b1
(macro m1 ((type ARG1))
(allow ARG1 self (C1 (P1a)))
)
)
(in after b1.m1
(allow ARG1 self (C1 (P1c)))
)
(type t1a)
(call b1.m1 (t1a))
(blockinherit b1)
(in after m1
(allow ARG1 self (C1 (P1b)))
)
(type t1b)
(call m1 (t1b))
;
This results in the following rules:
(allow t1a self (C1 (P1a)))
(allow t1a self (C1 (P1c)))
(allow t1b self (C1 (P1a)))
(allow t1b self (C1 (P1b)))
Block Example
;
(block b2
(block b
(type ta)
(allow ta self (C2 (P2a)))
)
)
(in before b2.b
(type tb)
(allow tb self (C2 (P2b)))
)
(block c2
(blockinherit b2)
(in after b
(type tc)
(allow tc self (C2 (P2c)))
)
)
;
This results in the following rules:
(allow b2.b.ta self (C2 (P2a)))
(allow b2.b.tb self (C2 (P2b)))
(allow c2.b.ta self (C2 (P2a)))
(allow c2.b.tb self (C2 (P2b)))
(allow c2.b.tc self (C2 (P2c)))
Using in-statements on optionals also works as expected.
One additional change is that blockabstract and blockinherit rules
are not allowed when using an after in-statement. This is because
both of those are resolved before an after in-statement would be
resolved.
Signed-off-by: James Carter <jwcart2@gmail.com>
Use a simpler recursive solution and set the head and tail pointers
of the starting node to NULL when done.
Remove the now uneeded setting of the head and tail pointers to NULL
in cil_resolve_in().
Signed-off-by: James Carter <jwcart2@gmail.com>
Refactor the function __cil_build_ast_node_helper() by moving the
check for illegal statements and the large if-then-else statement
to determine which function to call to parse the policy statements
to different functions.
There is no need to keep walking the nodes of a policy statement
that has already been completely parsed. This means that the
remaining nodes of any policy statement that does not contain a list
of policy statements can be skipped. This was done inconsistently
before. The following policy statements now have all nodes after
the first one skipped: blockinherit, blockabstract, classcommon,
user, userattribute, userbounds, userprefix, type, typeattribute,
typealias, typealiasactual, typebounds, typepermissive, role,
userrole, roletype, roletransition, roleallow, roleattribute,
rolebounds, bool, tunable, typetransition, typechange, typemember,
sensitivity, sensitivityalias, senistivityaliasactual, category,
categoryalias, categoryaliasactual, and ipaddr. The only policy
statements that do contain a list of policy statements are:
block, in, tunableif, booleanif, true (conditional block), false
(conditional block), macro, optional, and src_info.
Signed-off-by: James Carter <jwcart2@gmail.com>
If an optional that is to be disabled is the child of an optional that
is going to be disabled, then there is no reason to add that optional
to the stack of disabled optionals, because it is going to be destroyed
anyways. This means that there is no reason to maintain a stack of
disabled optionals at all.
Instead of using a stack to track disabled optionals, use a pointer
that points to the top-most optional that is to be disabled. When a
rule fails to resolve in an optional, if the disabled optional pointer
has not been set, then set it to that optional. If the pointer has
been set already, then the optional is already going to be destroyed,
so nothing else needs to be done. The resolution failure and the fact
that the optional is being disabled is reported in either case.
Signed-off-by: James Carter <jwcart2@gmail.com>
File names for typetransition rules are stored in their own datums.
This allows them to be passed as a parameter, but there needs to be
a check in __cil_insert_name() so that parameter names are not
mistaken for file name strings. This check did not verify that a
matching parameter name had the flavor of CIL_NAME.
Check that the parameter flavor is CIL_NAME and that the paramter
name matches the file name to be stored in the datum.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
A list is created to store type attribute datums when resolving an
expandtypeattribute rule and that list needs to be destroyed if the
AST is reset or a memory leak will occur.
Destroy the list storing type attributes datums when resetting
expandtypeattribute rules.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
The function cil_tree_get_next_path() does not check whether the
parse tree node that stores the high-level language file path of a
src_info rule actually exists before trying to read the path. This
can result in a NULL dereference.
Check that all of the parse tree nodes of a src_info rule exist
before reading the data from them.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
The commit d155b410d4 (libsepol/cil:
Check for duplicate blocks, optionals, and macros) added checks when
copying blocks, macros, and optionals so that a duplicate would cause
an exit with an error. Unfortunately, some policies exist that depend
on this behavior when using inheritance.
The behavior is as follows.
For macros only the first declared macro matters.
;
(macro m ((type ARG1))
(allow ARG1 self (CLASS (PERM1)))
)
(block b
(macro m ((type ARG1))
(allow ARG1 self (CLASS (PERM2)))
)
)
(blockinherit b)
(type t)
(call m (t))
;
For this policy segment, the macro m in block b will not be called.
Only the original macro m will be.
This behavior has been used to override macros that are going to
be inherited. Only the inherited macros that have not already been
declared in the destination namespace will be used.
Blocks seem to work fine even though there are two of them
;
(block b1
(blockinherit b2)
(block b
(type t1)
(allow t1 self (CLASS (PERM)))
)
)
(block b2
(block b
(type t2)
(allow t2 self (CLASS (PERM)))
)
)
(blockinherit b1)
;
In this example, the blockinherit of b2 will cause there to be
two block b's in block b1. Note that if both block b's tried to
declare the same type, then that would be an error. The blockinherit
of b1 will copy both block b's.
This behavior has been used to allow the use of in-statements for
a block that is being inherited. Since the in-statements are resolved
before block inheritance, this only works if a block with the same
name as the block to be inherited is declared in the namespace.
To support the use of these two behaviors, allow duplicate blocks
and macros when they occur as the result of block inheritance. In
any other circumstances and error for a redeclaration will be given.
Since the duplicate macro is not going to be used it is just skipped.
The duplicate block will use the datum of the original block. In both
cases a warning message will be produced (it will only be seen if
"-v" is used when compiling the policy).
Signed-off-by: James Carter <jwcart2@gmail.com>
In order to retain as much information as possible, when writing
out the CIL AST, use line mark notation to write out src_info
nodes. This includes using line marks to denote the original CIL
files the AST comes from.
The line numbers will not always be exactly correct because any
blank lines and comments in the original files will not be
represented in the AST.
Line marks are not written for the parse tree because the line
numbers will be widely inaccurate since each token will be on
a different line.
Signed-off-by: James Carter <jwcart2@gmail.com>
CIL supports specifiying the original high-level language file and
line numbers when reporting errors. This is done through line marks
and is mostly used to report the original Refpolicy file and line
number for neverallow rules that have been converted to CIL.
As long as the line mark remain simple, everything works fine, but
the wrong line numbers will be reported with more complex nextings
of line marks.
Example:
;;* lms 100 file01.hll
(type t1a)
(allow t1a self (CLASS (PERM)))
;;* lmx 200 file02.hll
(type t2a)
(allow t2a self (CLASS (PERM)))
;;* lme
(type t1b)
(allow t1b self (CLASS (PERM)))
(allow bad1b self (CLASS (PERM))) ; file01.hll:101 (Should be 106)
;;* lme
The primary problem is that the tree nodes can only store one hll
line number. Instead a number is needed that can be used by any
number of stacked line mark sections. This number would increment
line a normal line number except when in lmx sections (that have
the same line number throughout the section because they represent
an expansion of a line -- like the expansion of a macro call. This
number can go backwards when exiting a lms section within a lmx
section, because line number will increase in the lms section, but
outside the lmx section, the line number did not advance.
This number is called the hll_offset and this is the value that is
now stored in tree nodes instead of the hll line number. To calculate
the hll line number for a rule, a search is made for an ancestor of
the node that is a line mark and the line number for a lms section
is the hll line number stored in the line mark, plus the hll offset
of the rule, minus the hll offset of the line mark node, minus one.
(hll_lineno + hll_offset_rule - hll_offset_lm - 1)
Signed-off-by: James Carter <jwcart2@gmail.com>
To be able to write line mark information when writing the AST,
the line mark kind and line number is needed in the src info.
Instead of indicating whether the src info is for CIL or a hll,
differentiate between CIL, a normal hll line mark, and an expanded
hll line mark. Also include the line mark line number in the src
info nodes.
Signed-off-by: James Carter <jwcart2@gmail.com>
The functions cil_fill_integer() and cil_fill_integer64() exist in
cil_build_ast.c, but these functions take a node and it would be
better to have a function that can be used in add_hll_linemark()
so that the common functinality is in one place.
Create cil_string_to_uint32() and cil_string_to_uint64() and use
these functions in cil_fill_integer(), cil_fill_integer64(), and
add_hll_linemark().
Signed-off-by: James Carter <jwcart2@gmail.com>
CIL line mark rules are used to annotate the original line and file
of a rule. It is mostly used for neverallow rules that have been
converted to CIL.
Pushing the current line mark state after processing a line mark
section does not make sense since that information is never used.
When the line mark section ends the information is just popped and
discarded. It also makes pop_hll_info() more complicated than it
needs to be.
Push the line mark state first and simplfy pop_hll_info().
Signed-off-by: James Carter <jwcart2@gmail.com>
It clearer to check that the line mark type is a valid option right
after getting the token.
Check that the line mark type is one of the expected values right
awasy.
Signed-off-by: James Carter <jwcart2@gmail.com>
In add_hll_linemark(), cil_lexer_next() is called and the token
type is not checked after the call for the expected type (SYMBOL).
Check that the token type is SYMBOL after calling cil_lexer_next().
Signed-off-by: James Carter <jwcart2@gmail.com>
Every rule other than src_info has their syntax checked when
building the AST. It wasn't considered necessary for src_info rules
because they were expected to always be generated by the parser and
aren't part of the CIL language. But there is no check preventing
them from occurring in a policy and the secilc fuzzer found some bugs
by using src_info rules in a policy. This caused some syntax checking
to be added. Since the parse AST from secil2tree will contain src_info
rules and since the goal is to be able to compile the output of
secil2tree, it makes sense to check the syntax of src_info rules
in the same way that all of the other rules are checked.
Check the syntax of src_info statements in the same way every other
rule is checked.
Signed-off-by: James Carter <jwcart2@gmail.com>
It should make it easier to reproduce bugs found by OSS-Fuzz locally
without docker. The fuzz target can be built and run with the corpus
OSS-Fuzz has accumulated so far by running the following commands:
```
./scripts/oss-fuzz.sh
wget https://storage.googleapis.com/selinux-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/selinux_secilc-fuzzer/public.zip
unzip -d CORPUS public.zip
./out/secilc-fuzzer CORPUS/
```
It was tested in https://github.com/google/oss-fuzz/pull/6026
by pointing OSS-Fuzz to the branch containing the patch and
running all the tests with all the sanitizers and fuzzing engines
there: https://github.com/google/oss-fuzz/actions/runs/1024673143
[v2]
[1] oss-fuzz: make shellcheck happy
[2] oss-fuzz: build libsepol only
The fuzz target covers libsepol so it's unnecessary to build everything
else. Apart from that, the "LDFLAGS" kludge was removed since libsepol
is compatible with the sanitizers flags passed via CFLAGS only. It
should be brought back one way or another eventually though to fix
build failures like
```
clang -L/home/vagrant/selinux/selinux/DESTDIR/usr/lib -L/home/vagrant/selinux/selinux/DESTDIR/usr/lib -L../src sefcontext_compile.o ../src/regex.o -lselinux -lpcre ../src/libselinux.a -lsepol -o sefcontext_compile
/usr/bin/ld: sefcontext_compile.o: in function `usage':
/home/vagrant/selinux/selinux/libselinux/utils/sefcontext_compile.c:271: undefined reference to `__asan_report_load8'
/usr/bin/ld: /home/vagrant/selinux/selinux/libselinux/utils/sefcontext_compile.c:292: undefined reference to `__asan_handle_no_return'
/usr/bin/ld: sefcontext_compile.o: in function `asan.module_ctor':
```
[3] oss-fuzz: make it possible to run the script more than once
by removing various build artifacts
[4] oss-fuzz: make it possible to run the script from any directory
[5] oss-fuzz: be a little bit more specific about what the script does
[6] oss-fuzz: stop overwriting all the Makefiles
Signed-off-by: Evgeny Vereshchagin <evvers@ya.ru>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
The standard function `strerror(3)` is not thread safe. This does not
only affect the concurrent usage of libselinux itself but also with
other `strerror(3)` linked libraries.
Use the thread safe GNU extension format specifier `%m`[1].
libselinux already uses the GNU extension format specifier `%ms`.
[1]: https://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Commit a60343cabf ("libsepol/cil: remove unnecessary hash tables")
removed FILENAME_TRANS_TABLE_SIZE macro that this comment was referring
to. Remove the comment as well to avoid confusion.
Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
Using the '\0' character in strings in a CIL policy is not expected to
happen, and makes the flex tokenizer very slow. For example when
generating a file with:
python -c 'print("\"" + "\0"*100000 + "\"")' > policy.cil
secilc fails after 26 seconds, on my desktop computer. Increasing the
numbers of \0 makes this time increase significantly. But replacing \0
with another character makes secilc fail in only few milliseconds.
Fix this "possible denial of service" issue by forbidding \0 in strings
in CIL policies.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=36016
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
There are two problems that need to be addressed when resolving an
expression with category sets.
1. Only expand anonymous category sets in an expression.
Commit 982ec302b6 (libsepol/cil:
Account for anonymous category sets in an expression) attempted to
properly handle anonymous category sets when resolving category
expressions. Unfortunately, it did not check whether a category set
was actually an anonymous category set and expanded all category
sets in an expression. If a category set refers to itself in the
expression, then everything from the name of the category set to the
end of the expression is ignored.
For example, the rule "(categoryset cs (c0 cs c1 c2))", would be
equivalent to the rule "(categoryset cs (c0))" as everything from
"cs" to the end would be dropped. The secilc-fuzzer found that the
rule "(categoryset cat (not cat))" would cause a segfault since
"(not)" is not a valid expression and it is assumed to be valid
during later evaluation because syntax checking has already been
done.
Instead, check whether or not the category set is anonymous before
expanding it when resolving an expression.
2. Category sets cannot be used in a category range
A category range can be used to specify a large number of categories.
The range "(range c0 c1023)" refers to 1024 categories. Only categories
and category aliases can be used in a range. Determining if an
identifier is a category, an alias, or a set can only be done after
resolving the identifer.
Keep track of the current operator as an expression is being resolved
and if the expression involves categories and a category set is
encountered, then return an error if the expression is a category
range.
Signed-off-by: James Carter <jwcart2@gmail.com>
Clang complains:
ibendport_record.c: In function ‘sepol_ibendport_get_ibdev_name’:
ibendport_record.c:169:2: error: ‘strncpy’ specified bound 64 equals destination size [-Werror=stringop-truncation]
169 | strncpy(tmp_ibdev_name, ibendport->ibdev_name, IB_DEVICE_NAME_MAX);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ibendport_record.c: In function ‘sepol_ibendport_set_ibdev_name’:
ibendport_record.c:189:2: error: ‘strncpy’ specified bound 64 equals destination size [-Werror=stringop-truncation]
189 | strncpy(tmp, ibdev_name, IB_DEVICE_NAME_MAX);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
strncpy(3) does not NUL-terminate the destination if the source is of
the same length or longer then the specified size.
The source of these copies are retrieved from
sepol_ibendport_alloc_ibdev_name(), which allocates a fixed amount of
IB_DEVICE_NAME_MAX bytes.
Reduce the size to copy by 1 of all memory regions allocated by
sepol_ibendport_alloc_ibdev_name().
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Avoid implicit conversions from signed to unsigned values, found by
UB sanitizers, by using unsigned values in the first place.
expand.c:1644:18: runtime error: implicit conversion from type 'int' of value -1 (32-bit, signed) to type 'uint32_t' (aka 'unsigned int') changed the value to 4294967295 (32-bit, unsigned)
expand.c:2892:24: runtime error: implicit conversion from type 'int' of value -2 (32-bit, signed) to type 'unsigned int' changed the value to 4294967294 (32-bit, unsigned)
policy_define.c:2344:4: runtime error: implicit conversion from type 'int' of value -1048577 (32-bit, signed) to type 'unsigned int' changed the value to 4293918719 (32-bit, unsigned)
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Unsigned integer overflow is well-defined and not undefined behavior.
But it is still useful to enable undefined behavior sanitizer checks on
unsigned arithmetic to detect possible issues on counters or variables
with similar purpose.
Annotate functions, in which unsigned overflows are expected to happen,
with the respective Clang function attribute[1].
GCC does not support sanitizing unsigned integer arithmetic[2].
avtab.c:76:2: runtime error: unsigned integer overflow: 6 * 3432918353 cannot be represented in type 'unsigned int'
policydb.c:795:42: runtime error: unsigned integer overflow: 8160943042179512010 * 11 cannot be represented in type 'unsigned long'
symtab.c:25:12: runtime error: left shift of 1766601759 by 4 places cannot be represented in type 'unsigned int'
[1]: https://clang.llvm.org/docs/AttributeReference.html#no-sanitize
[2]: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Unsigned integer overflow is well-defined and not undefined behavior.
It is commonly used for hashing or pseudo random number generation.
But it is still useful to enable undefined behavior sanitizer checks on
unsigned arithmetic to detect possible issues on counters or variables
with similar purpose or missed overflow checks on user input.
Use a spaceship operator like comparison instead of subtraction.
policydb.c:851:24: runtime error: unsigned integer overflow: 801 - 929 cannot be represented in type 'unsigned int'
Follow-up of: 1537ea8412 ("libsepol: avoid unsigned integer overflow")
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
commits 37863b0b14 (libsepol/cil:
Improve degenerate inheritance check) and
74d00a8dec (libsepol/cil: Detect
degenerate inheritance and exit with an error) attempted to detect
and exit with an error when compiling policies that have degenerate
inheritances. These policies result in the exponential growth of memory
usage while copying the blocks that are inherited.
There were two problems with the previous attempts to detect this
bad inheritance problem. The first is that the quick check using
cil_possible_degenerate_inheritance() did not detect all patterns
of degenerate inheritance. The second problem is that the detection
of inheritance loops during the CIL_PASS_BLKIN_LINK pass did not
detect all inheritance loops which made it possible for the full
degenerate inheritance checking done with
cil_check_for_degenerate_inheritance() to have a stack overflow
when encountering the inheritance loops. Both the degenerate and
loop inheritance checks need to be done at the same time and done
after the CIL_PASS_BLKIN_LINK pass. Otherwise, if loops are being
detected first, then a degenerate policy can cause the consumption
of all system memory and if degenerate policy is being detected
first, then an inheritance loop can cause a stack overflow.
With the new approach, the quick check is eliminated and the full
check is always done after the CIL_PASS_BLKIN_LINK pass. Because
of this the "inheritance_check" field in struct cil_resolve_args
is not needed and removed and the functions
cil_print_recursive_blockinherit(), cil_check_recursive_blockinherit(),
and cil_possible_degenerate_inheritance() have been deleted. The
function cil_count_potential() is renamed cil_check_inheritances()
and has checks for both degenerate inheritance and inheritance loops.
The inheritance checking is improved and uses an approach similar
to commit c28525a26f (libsepol/cil:
Properly check for loops in sets).
As has been the case with these degenerate inheritance patches,
these issues were discovered by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
On Ubuntu 20.04, when building with clang -Werror -Wextra-semi-stmt
(which is not the default build configuration), the compiler reports:
../cil/src/cil_binary.c:4293:22: error: empty expression statement
has no effect; remove unnecessary ';' to silence this warning
[-Werror,-Wextra-semi-stmt]
mix(k->target_class);
^
../cil/src/cil_binary.c:4294:21: error: empty expression statement
has no effect; remove unnecessary ';' to silence this warning
[-Werror,-Wextra-semi-stmt]
mix(k->target_type);
^
../cil/src/cil_binary.c:4295:21: error: empty expression statement
has no effect; remove unnecessary ';' to silence this warning
[-Werror,-Wextra-semi-stmt]
mix(k->source_type);
^
../cil/src/cil_binary.c:4296:19: error: empty expression statement
has no effect; remove unnecessary ';' to silence this warning
[-Werror,-Wextra-semi-stmt]
mix(k->specified);
^
Use a do { ... } while (0) construction to silence this warning.
Moreover the same warning appears when using two semicolons to end a
statement. Remove such occurrences, like what was already done in commit
811185648a ("libsepol: drop repeated semicolons").
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
When __cil_verify_map_class() verifies a classpermission, it calls
__verify_map_perm_classperms() on each item. If the first item reports a
failure and the next one succeeds, the failure is overwritten in
map_args->rc. This is a bug which causes a NULL pointer dereference in
the CIL compiler when compiling the following policy:
(sid SID)
(sidorder (SID))
(class CLASS (PERM1))
(classorder (CLASS))
(classpermission CLSPERM)
(classpermissionset CLSPERM (CLASS (PERM1)))
(classmap files (CLAMAPxx x))
(classmapping files CLAMAPxx CLSPERM)
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=30286
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Qualified names have "dots" in them. They are generated when a CIL
policy is compiled and come from declarations in blocks. If a kernel
policy is decompiled into a CIL policy, the resulting policy could
have declarations that use qualified names. Compiling this policy would
result in an error because "dots" in declarations are not allowed.
Qualified names in a policy are normally used to refer to the name of
identifiers, blocks, macros, or optionals that are declared in a
different block (that is not a parent). Name resolution is based on
splitting a name based on the "dots", searching the parents up to the
global namespace for the first block using the first part of the name,
using the second part of the name to lookup the next block using the
first block's symbol tables, looking up the third block in the second's
symbol tables, and so on.
To allow the option of using qualified names in declarations:
1) Create a field in the struct cil_db called "qualified_names" which
is set to CIL_TRUE when qualified names are to be used. This field is
checked in cil_verify_name() and "dots" are allowed if qualified names
are being allowed.
2) Only allow the direct lookup of the whole name in the global symbol
table. This means that blocks, blockinherits, blockabstracts, and in-
statements cannot be allowed. Use the "qualified_names" field of the
cil_db to know when using one of these should result in an error.
3) Create the function cil_set_qualified_names() that is used to set
the "qualified_names" field. Export the function in libsepol.
Signed-off-by: James Carter <jwcart2@gmail.com>
When disabling optionals, the AST needs to be reset only if one
of the optional blocks being disabled contains a declaration.
Call the function cil_tree_subtree_has_decl() for each optional
block being disabled and only reset the AST if one of them has
a declaration in it.
Signed-off-by: James Carter <jwcart2@gmail.com>
Create the function cil_tree_subtree_has_decl() that returns CIL_TRUE
if the subtree has a declaration in it and CIL_FALSE otherwise.
Signed-off-by: James Carter <jwcart2@gmail.com>
Use one of the policy config bits to tell the kernel to start using
the nlmsg_readneigh on RTM_GETNEIGH and RTM_GETNEIGHTBL messages instead
of the previous behavior of using nlmsg_read.
Bug: 171572148
Test: atest NetworkInterfaceTest
Test: atest bionic-unit-tests-static
Test: atest CtsSelinuxTargetSdkCurrentTestCases
Test: atest CtsSelinuxTargetSdk30TestCases
Test: atest CtsSelinuxTargetSdk29TestCases
Test: atest CtsSelinuxTargetSdk28TestCases
Test: atest CtsSelinuxTargetSdk27TestCases
Test: atest CompatChangesSelinuxTest
Test: atest NetlinkSocketTest
Test: On Cuttlefish, run combinations of:
- Policy bit set or omitted
- App having nlmsg_readneigh permission or not
Verify that only the combination of the policy bit being set + the app
not having the nlmsg_readneigh permission prevents the app from
sending RTM_GETNEIGH messages.
Change-Id: I1b0e2398f12e9dd9872c9b916efa76d22f85d56b
The commit 74d00a8dec (libsepol/cil:
Detect degenerate inheritance and exit with an error) detects the
use of inheritance (mostly by the secilc-fuzzer and not in any real
policies) that results in the exponential growth of the policy through
the copying of blocks that takes place with inheritance in CIL.
Unfortunately, the check takes place during the pass when all the
blocks are being copied, so it is possible to consume all of a system's
memory before an error is produced.
The new check happens in two parts. First, a check is made while the
block inheritance is being linked to the block it will inherit. In
this check, all of the parent nodes of the inheritance rule up to the
root node are checked and if enough of these blocks are being inherited
(>= CIL_DEGENERATE_INHERITANCE_DEPTH), then a flag is set for a more
in-depth check after the pass. This in-depth check will determine the
number of potential inheritances that will occur when resolving the
all of the inheritance rules. If this value is greater than
CIL_DEGENERATE_INHERITANCE_GROWTH * the original number of inheritance
rules and greater than CIL_DEGENERATE_INHERITANCE_MINIMUM (which is
set to 0x1 << CIL_DEGENERATE_INHERITANCE_DEPTH), then degenerate
inheritance is determined to have occurred and an error result will
be returned.
Since the potential number of inheritances can quickly be an extremely
large number, the count of potential inheritances is aborted as soon
as the threshold for degenerate inheritance has been exceeded.
Normal policies should rarely, if ever, have the in-depth check occur.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
It is possible to create bad behaving policy that can consume all
of a system's memory (one way is through the use of inheritance).
Analyzing these policies shows that most of the memory usage is for
the block symtabs.
Most of the nineteen symtabs will most likely never be used, so give
these symtabs an initial size of 1. The others are given more
appropriate sizes.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
When marking a type attribute as used in a neverallow (to help determine
whether or not it should be expanded), check if the attribute's expression
list is empty (no attributes are associated with it) before iterating
over the list.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
When "glblub" was added as a default for the defaultrange rule, the
syntax array was updated because the "glblub" default does not need
to specify a range of "low", "high", or "low-high". Unfortunately,
additional checking was not added for the "source" and "target"
defaults to make sure they specified a range. This means that using
the "source" or "target" defaults without specifying the range will
result in a segfault.
When the "source" or "target" defaults are used, check that the rule
specifies a range as well.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Commit 61fbdce666 (ibsepol/cil: Check
for self-referential loops in sets) added checks for self-referential
loops in user, role, type, and category sets. Unfortunately, this
check ends up in an infinite loop if the set with the self-referential
loop is used in a different set that is checked before the bad set.
The problem with the old check is that only the initial datum is used
for the check. Instead, use a stack to track all of the set datums
that are currently involved as the check is made. A self-referential
loop occurs if a duplicate datum is found for any of the datums in the
stack.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
The commit d155b410d4 (libsepol/cil:
Check for duplicate blocks, optionals, and macros) fixed a bug
that allowed duplicate blocks, optionals, and macros with the same
name in the same namespace. For blocks and macros, a duplicate
is always a problem, but optional block names are only used for
in-statement resolution. If no in-statement refers to an optional
block, then it does not matter if more than one with same name
exists.
One easy way to generate multiple optional blocks with the same
name declaration is to call a macro with an optional block multiple
times in the same namespace.
As an example, here is a portion of CIL policy
(macro m1 ((type t))
(optional op1
(allow t self (CLASS (PERM)))
)
)
(type t1)
(call m1 (t1))
(type t2)
(call m1 (t2))
This will result in two optional blocks with the name op1.
There are three parts to allowing multiple optional blocks with
the same name declaration.
1) Track an optional block's enabled status in a different way.
One hinderance to allowing multiple optional blocks with the same
name declaration is that they cannot share the same datum. This is
because the datum is used to get the struct cil_optional which has
the enabled field and each block's enabled status is independent of
the others.
Remove the enabled field from struct cil_optional, so it only contains
the datum. Use a stack to track which optional blocks are being
disabled, so they can be deleted in the right order.
2) Allow multiple declarations of optional blocks.
Update cil_allow_multiple_decls() so that a flavor of CIL_OPTIONAL
will return CIL_TRUE. Also remove the check in cil_copy_optional().
3) Check if an in-statement refers to an optional with multiple
declarations and exit with an error if it does.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Make it more apparent that those data does not change and enforce it.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Clang issues:
module_to_cil.c:65:7: warning: no previous extern declaration for non-static variable 'out_file' [-Wmissing-variable-declarations]
FILE *out_file;
^
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
`hashtab_search()` does take `const_hashtab_key_t` as second parameter,
which is a typedef for `const char *`.
Drop the unnecessary and const-violating cast.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Initialize variables, as they are set after goto statements, which jump
to cleanup code using them.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
`const_hashtab_key_t` is a typedef of `const char *`, so these casts are
not needed.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
../cil/src/cil_binary.c:2230:24: warning: Value stored to 'cb_node' during its initialization is never read [deadcode.DeadStores]
struct cil_tree_node *cb_node = node->cl_head;
^~~~~~~ ~~~~~~~~~~~~~
Found by clang-analyzer
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
../cil/src/cil_write_ast.c:86:32: error: cast to smaller integer type 'enum cil_flavor' from 'void *' [-Werror,-Wvoid-pointer-to-enum-cast]
enum cil_flavor op_flavor = (enum cil_flavor)curr->data;
^~~~~~~~~~~~~~~~~~~~~~~~~~~
../cil/src/cil_write_ast.c:130:37: error: cast to smaller integer type 'enum cil_flavor' from 'void *' [-Werror,-Wvoid-pointer-to-enum-cast]
enum cil_flavor operand_flavor = (enum cil_flavor)curr->data;
^~~~~~~~~~~~~~~~~~~~~~~~~~~
Silence this warning by casting the pointer to an integer the cast to
enum cil_flavor.
See 32f8ed3d6b ("libsepol/cil: introduce intermediate cast to silence -Wvoid-pointer-to-enum-cast")
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
In case cats_ebitmap_len() returns 0, do not allocate but quit.
Found by clang-analyzer
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Make it more obvious which parameters are read-only and not being
modified and allow callers to pass const pointers.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Make it more obvious which parameters are read-only and not being
modified and allow callers to pass const pointers.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
conditional.c:391:4: warning: Value stored to 'i' is never read [deadcode.DeadStores]
i = 0;
^ ~
conditional.c:718:2: warning: Value stored to 'len' is never read [deadcode.DeadStores]
len = 0;
^ ~
conditional.c:772:2: warning: Value stored to 'len' is never read [deadcode.DeadStores]
len = 0;
^ ~
services.c:89:10: warning: Value stored to 'new_stack' during its initialization is never read [deadcode.DeadStores]
char **new_stack = stack;
^~~~~~~~~ ~~~~~
services.c:440:11: warning: Value stored to 'new_expr_list' during its initialization is never read [deadcode.DeadStores]
char **new_expr_list = expr_list;
^~~~~~~~~~~~~ ~~~~~~~~~
../cil/src/cil_binary.c:2230:24: warning: Value stored to 'cb_node' during its initialization is never read [deadcode.DeadStores]
struct cil_tree_node *cb_node = node->cl_head;
^~~~~~~ ~~~~~~~~~~~~~
Found by clang-analyzer
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Follow the project style of no declaration after statement.
Found by the gcc warning -Wdeclaration-after-statement
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Follow the project style of no declaration after statement.
Found by the gcc warning -Wdeclaration-after-statement
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Unsigned integer overflow is well-defined and not undefined behavior.
But it is still useful to enable undefined behavior sanitizer checks on
unsigned arithmetic to detect possible issues on counters or variables
with similar purpose.
Use a spaceship operator like comparison instead of subtraction.
Modern compilers will generate a single comparison instruction instead
of actually perform the subtraction.
policydb.c:826:17: runtime error: unsigned integer overflow: 24 - 1699 cannot be represented in type 'unsigned int'
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
The functions `role_set_get_role`, `sepol_validate_transition` and
`sepol_sidtab_remove` seem to be unused since the initial import.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Declare the functions as static or include the corresponding header
file.
assertion.c:294:5: error: no previous prototype for function 'report_assertion_failures' [-Werror,-Wmissing-prototypes]
int report_assertion_failures(sepol_handle_t *handle, policydb_t *p, avrule_t *avrule)
^
context.c:23:5: error: no previous prototype for function 'sepol_check_context' [-Werror,-Wmissing-prototypes]
int sepol_check_context(const char *context)
^
expand.c:3377:5: error: no previous prototype for function 'expand_cond_av_node' [-Werror,-Wmissing-prototypes]
int expand_cond_av_node(policydb_t * p,
^
policydb.c:638:6: error: no previous prototype for function 'role_trans_rule_destroy' [-Werror,-Wmissing-prototypes]
void role_trans_rule_destroy(role_trans_rule_t * x)
^
policydb.c:1169:5: error: no previous prototype for function 'policydb_index_decls' [-Werror,-Wmissing-prototypes]
int policydb_index_decls(sepol_handle_t * handle, policydb_t * p)
^
policydb.c:1429:6: error: no previous prototype for function 'ocontext_selinux_free' [-Werror,-Wmissing-prototypes]
void ocontext_selinux_free(ocontext_t **ocontexts)
^
policydb.c:1451:6: error: no previous prototype for function 'ocontext_xen_free' [-Werror,-Wmissing-prototypes]
void ocontext_xen_free(ocontext_t **ocontexts)
^
policydb.c:1750:5: error: no previous prototype for function 'type_set_or' [-Werror,-Wmissing-prototypes]
int type_set_or(type_set_t * dst, type_set_t * a, type_set_t * b)
^
policydb.c:2524:5: error: no previous prototype for function 'role_trans_read' [-Werror,-Wmissing-prototypes]
int role_trans_read(policydb_t *p, struct policy_file *fp)
^
policydb.c:2567:5: error: no previous prototype for function 'role_allow_read' [-Werror,-Wmissing-prototypes]
int role_allow_read(role_allow_t ** r, struct policy_file *fp)
^
policydb.c:2842:5: error: no previous prototype for function 'filename_trans_read' [-Werror,-Wmissing-prototypes]
int filename_trans_read(policydb_t *p, struct policy_file *fp)
^
services.c:1027:5: error: no previous prototype for function 'sepol_validate_transition' [-Werror,-Wmissing-prototypes]
int sepol_validate_transition(sepol_security_id_t oldsid,
^
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Christian Göttsche <cgzones@googlemail.com> submitted a similar patch
to quote paths when generating CIL policy from a binary policy.
Since genfscon and devicetreecon rules have paths which are allowed
to contain spaces, always quote the path when writing out these rules.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Petr Lautrbach <plautrba@redhat.com>
It is possible for anonymous category sets to be in a category
expression if the expression has a macro parameter in it.
Unfortunately, anonymous category sets are not looked for when
resolving category expressions and a segfault will occur during
later processing if there was one.
As an example, consider the following portion of a policy.
(macro m1 ((categoryset cs))
(userlevel USER (s0 (cs)))
)
(call m1 ((c0 c1)))
This policy will cause a segault, because the categoryset datum
for the parameter cs is not seen as a categoryset and is treated
as a plain category.
When resolving an expression, check whether or not the datum that
is found is actually an anonymous category set associated with a
macro parameter. If it is, then resolve the category set if it
has not already been resolved and treat its categories as a sub
expression.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
A named IP address (using an ipaddr rule) could be passed as an
argument, but trying to pass an actual IP address caused an error.
As an exmample, consider the following portion of a policy.
(macro m4 ((ipaddr ip)(ipaddr nm))
(nodecon ip nm (USER ROLE TYPE ((s0) (s0))))
)
(ipaddr nm1 255.255.255.0)
(ipaddr ip1 1.2.3.4)
(call m4 (ip1 nm1)) ; This works
(call m4 (1.2.3.4 255.255.255.0)) ; This doesn't
Allow actual IP addresses to be passed as a call argument. Now the
second call works as well.
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
When generating CIL policy from kernel or module policy quote paths,
which are allowed to contain spaces, in the statements `genfscon` and
`devicetreecon`.
Reported by LuK1337 while building policy for Android via IRC.
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Anonymous levels can be passed as call arguments and they can
appear in anonymous levelranges as well.
Anonymous call arguments are resolved when they are used in a rule.
If more than one rule uses the anonymous level, then a memory leak
will occur when a new list for the category datum expression is
created without destroying the old one.
When resolving a level, check if the sensitivity datum has already
been resolved. If it has, then the categories have been as well.
Signed-off-by: James Carter <jwcart2@gmail.com>
Set the pointer to the sensitivity in levels, the pointers to the low
and high levels in levelranges, the pointer to the level in userlevels,
the pointer to the range in userranges, and the pointers to contexts
in ocontexts to NULL.
Signed-off-by: James Carter <jwcart2@gmail.com>
Anonymous class permission sets can be passed as call arguments.
Anonymous call arguments are resolved when they are used in a
rule. [This is because all the information might not be present
(like common permissions being added to a class) when the call
itself is resolved.] If there is more than one rule using an
anonymous class permission set, then a memory leak will occur
when a new list for the permission datum expression is created
without destroying the old one.
When resolving the class and permissions, check if the class has
already been resolved. If it has, then the permissions have been
as well.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
When parsing a CIL policy, the number of open parenthesis is tracked
to verify that each has a matching close parenthesis. If there are
too many open parenthesis, a stack overflow could occur during later
processing.
Exit with an error if the number of open parenthesis exceeds 4096
(which should be enough for any policy.)
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
When exiting with an error because a class or common has too many
permissions, destroy the permission nodes.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
A failed tunable resolution in a tunableif can cause an optional
to be disabled before the CIL_PASS_CALL1 phase. If this occurs, the
optional block and its subtree should be destroyed, but no reset
will be required since tunables are not allowed inside an optional
block.
Anytime there are optional blocks in the disabled_optionals list
(changed == 1), destroy the optional block and its subtree even if
in a pass before CIL_PASS_CALL1.
This bug was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
Lorenzo Ceragioli <lorenzo.ceragioli@phd.unipi.it> noted that the
following policy:
(type a)
(block A
(macro m ((type x))
(type a)
(allow x x (file (read))))
)
(block B
(call A.m(a))
)
results in the allow rule (allow B.a B.a (file(read))). This makes
no sense because the "a" being passed as an argument has to be the
global "a" and not the "a" defined in the macro.
This behavior occurs because the call arguments are resolved AFTER
the macro body has been copied and the declaration of "a" in the
macro has been added to block B's namespace, so this is the "a"
that the call argument resolves to, rather than the one in the
global namespace.
When resolving call arguments, check if the datum found belongs to
a declaration in the call. If it does, then remove the datum from
the symbol table, re-resolve the argument, and add the datum back
into the symbol table.
Signed-off-by: James Carter <jwcart2@gmail.com>
Rename cil_resolve_call1() as cil resolve_call() and rename
cil_resolve_call2() as cil_resolve_call_args() to make it clearer
what is being done in each function.
Move code to build call arguments out of cil_resolve_call() and into
the new function called cil_build_call_args() so that the logic of
cil_resolve_call() can be seen.
Exit cil_resolve_call() immediately if the call has already been
copied.
In __cil_resolve_ast_node(), only resolve calls outside of macros.
This results in more calls to cil_copy_ast(), but slightly less
rules copied overall (since no rules are copied into a macro). This
also means that the CIL_PASS_MACRO pass is not needed and can be
eliminated.
Signed-off-by: James Carter <jwcart2@gmail.com>
Allow inserting a key without providing a node.
This will make it easier to properly resolve call arguments where
a key might need to be temporarily removed to search for a datum
that is not declared within the call. Since the node is already
in the node list, re-inserting the key without this option would
add another link to the node and cause problems.
Also, do not add the node to the datum's node list if the result
of the call to hashtab_insert() is SEPOL_EEXIST because the datum
is a duplicate and will be destroyed.
Signed-off-by: James Carter <jwcart2@gmail.com>
The CIL Reference Guide specifies how name resolution is suppose
to work within an expanded macro.
1. Items defined inside the macro
2. Items passed into the macro as arguments
3. Items defined in the same namespace of the macro
4. Items defined in the caller's namespace
5. Items defined in the global namespace
But Lorenzo Ceragioli <lorenzo.ceragioli@phd.unipi.it> found
that the first step is not done.
So the following policy:
(block A
(type a)
(macro m ()
(type a)
(allow a self (CLASS (PERM)))
)
)
(block B
(call A.m)
)
will result in:
(allow A.a self (CLASS (PERM)))
instead of the expected:
(allow B.a self (CLASS (PERM)))
Now when an expanded call is found, the macro's namespace is
checked first. If the name is found, then the name was declared
in the macro and it is declared in the expanded call, so only the
namespace of the call up to and including the global namespace
will be searched. If the name is not found in the macro's namespace
then name resolution continues with steps 2-5 above.
Signed-off-by: James Carter <jwcart2@gmail.com>
When resolving a name in a block that has been inherited. First,
a search is done in the parent namespaces (if any) of the
blockinherit rule with the exception of the global namespace. If
the name is not found, then a search is done in the namespaces of
the original block (starting with that block's namespace) with
the exception of the global namespace. Finally, if it still has
not been found, the global namespace is searched.
This does not work if a declaration is in the block being
inherited.
For example:
(block b
(typeattribute a)
(allow a self (CLASS (PERM)))
)
(blockinherit b)
This will result in a policy with the following identical allow
rules:
(allow b.a self (CLASS (PERM)))
(allow b.a self (CLASS (PERM)))
rather than the expected:
(allow b.a self (CLASS (PERM)))
(allow a self (CLASS (PERM)))
This is because when the typeattribute is copied while resolving
the inheritance, the new datum is added to the global namespace
and, since that is searched last, the typeattribute in block b is
found first.
This behavior means that no declaration that is inherited into the
global namespace will actually be used.
Instead, if the name is not found in the parent namespaces (if any)
where the blockinherit is located with the exception of the global
namespace, start the next search in the namespace of the parent of
the original block (instead of the original block itself). Now if
a declaration is inherited from the original block, the new
declaration will be used. This behavior seems to be the originally
intended behavior because there is a comment in the code that says,
"Continue search in original block's parent".
This issue was found by secilc-fuzzer. If the original block
is made to be abstract, then the type attribute declaration
in the original block is not in the policy and a segfault
occurs when creating the binary because the copied allow rule
refers to a non-existent type attribute.
Signed-off-by: James Carter <jwcart2@gmail.com>
Use one of the policy config bits to tell the kernel to start using
the nlmsg_readneigh on RTM_GETNEIGH and RTM_GETNEIGHTBL messages instead
of the previous behavior of using nlmsg_read.
Bug: 171572148
Test: atest NetworkInterfaceTest
Test: atest bionic-unit-tests-static
Test: atest CtsSelinuxTargetSdkCurrentTestCases
Test: On Cuttlefish, run combinations of:
- Policy bit set or omitted
- App having nlmsg_readneigh permission or not
Verify that only the combination of the policy bit being set + the app
not having the nlmsg_readneigh permission prevents the app from
sending RTM_GETNEIGH messages.
Change-Id: I8598662b795feaeaeb8b0a7e676b684022861c37
The secilc-fuzzer found a self-referential loop using category sets.
Any set declaration in CIL that allows sets in it is susceptible to
the creation of a self-referential loop. There is a check, but only
for the name of the set being declared being used in the set
declaration.
Check for self-refential loops in user, role, and type attributes
and in category sets. Since all of the sets need to be declared,
this check has to be done when verifying the CIL db before doing
the post phase.
Signed-off-by: James Carter <jwcart2@gmail.com>
Return an error if a call argument fails to resolve so that
the resolution phase stops and returns an error.
This problem was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
The secilc-fuzzer found an out of bounds memory access occurs
when building the binary policy if a map class is included in a
classorder statement.
The order statements in CIL (sidorder, classorder, categoryorder,
and sensitivityorder) are used to specify an ordering for sids,
classes, categories, and sensitivities. When the order statments
are resolved and merged, only in the case of the category order
list is the datum resolved checked to see if it is the expected
flavor.
When resolving the sid, class, and sensitivity order statements,
check that each name resolved to a datum of the expected flavor
and return an error if it does not.
Signed-off-by: James Carter <jwcart2@gmail.com>
A CIL policy with inheritance of the form
...
(blockinherit ba)
(block ba
(block b1
(blockinherit bb)
)
(block bb
(block b2
(blockinherit bc)
)
(block bc
(block b3
(blockinherit bd)
)
(block bd
(block b4
(blockinherit be)
)
(block be
...
will require creating 2^depth copies of the block at the bottom of
the inheritance chain. This pattern can quickly consume all the
memory of the system compiling this policy.
The depth of the inheritance chain can be found be walking the
tree up through the parents and noting how many of the parent
blocks have been inherited. The number of times a block will be
copied is found by counting the list of nodes in the "bi_nodes"
list of the block. To minimize legitimate policies from being
falsely detected as being degenerate, both the depth and breadth
(number of copies) are checked and an error is given only if both
exceed the limits (depth >= 12 and breadth >= 4096).
This problem was found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
There are six instances when the CIL policy is being built or
resolved where an error can be detected, but SEPOL_OK is returned
instead of SEPOL_ERR. This causes the policy compiler to continue
when it should exit with an error.
Return SEPOL_ERR in these cases, so the compiler exits with an
error.
Two of the instances were found by the secilc-fuzzer.
Signed-off-by: James Carter <jwcart2@gmail.com>
In struct cil_classperms_set, the "set" field is a pointer to a
struct cil_classpermission. Normally the classpermission is created
in a classpermissionset rule with a name declared in a
classpermission rule and stored in a symbol table. Commit c49a8ea0
("libsepol/cil: cil_reset_classperms_set() should not reset
classpermission") fixed the resetting of classperms sets by setting
the "set" field to NULL rather than resetting the classpermission
that it pointed to.
But this fix mixed the special case where an anonymous classperm
set is passed as an argument to a call. In this case the
classpermission is not named and not stored in a symtab, it is
created just for the classperms set and its classperms list needs
to be reset.
Reset the classperms list if the classperms set is anonymous (which
is when the datum name is NULL).
Signed-off-by: James Carter <jwcart2@gmail.com>
Add the functions cil_write_parse_ast(), cil_write_build_ast(),
and cil_write_resolve_ast() that can be used outside of libsepol.
These functions take a FILE pointer and CIL db, do the CIL build
through the desired phase, and then call cil_write_ast() to write
the CIL AST at that point.
Signed-off-by: James Carter <jwcart2@gmail.com>
The function cil_print_tree() has existed in cil_tree.c since the
beginning of the development of CIL and secilc. Unfortunately, it
used cil_log() at log level CIL_INFO to print out the AST and
has suffered greatly from bit rot.
Move the functions to write the CIL AST to cil_write_ast.c, update
the functions, and write the AST to the FILE pointer passed in as
an argument.
The function cil_write_ast() supports writing the CIL AST at three
different phases of the compiling a CIL policy. After parsing has
been done and the parse tree has been created, after the CIL AST
has been built, and after the CIL AST has been resolved.
Signed-off-by: James Carter <jwcart2@gmail.com>
In cil_compile(), CIL_INFO is being used as the priority for
error messages. This can make it difficult to tell when the error
occurred.
Instead, use CIL_ERR as the priority for the error messages in
cil_compile().
Signed-off-by: James Carter <jwcart2@gmail.com>
Use a consistent style for the error messages when an invalid
statement is found within tunableif, in-statement, block, macro,
optional, and booleanif blocks.
Signed-off-by: James Carter <jwcart2@gmail.com>
Since tunableifs are resolved before in-statements, do not allow
tuanble declarations in in-statements.
Since in-statements are the first flavor of statement that causes
part of the AST to be copied to another part, there is no need to
check the in-statements when resolving the AST.
Signed-off-by: James Carter <jwcart2@gmail.com>
When resolving the AST, tunable and in-statements are not considered
to be invalid in macros. This is inconsistent with the checks when
building the AST.
Add checks to make tunable and in-statments invalid in macros when
resolving the AST.
Signed-off-by: James Carter <jwcart2@gmail.com>
While there are some checks for invalid statements in an optional
block when resolving the AST, there are no checks when building the
AST.
OSS-Fuzz found the following policy which caused a null dereference
in cil_tree_get_next_path().
(blockinherit b3)
(sid SID)
(sidorder(SID))
(optional o
(ibpkeycon :(1 0)s)
(block b3
(filecon""block())
(filecon""block())))
The problem is that the blockinherit copies block b3 before
the optional block is disabled. When the optional is disabled,
block b3 is deleted along with everything else in the optional.
Later, when filecon statements with the same path are found an
error message is produced and in trying to find out where the block
was copied from, the reference to the deleted block is used. The
error handling code assumes (rightly) that if something was copied
from a block then that block should still exist.
It is clear that in-statements, blocks, and macros cannot be in an
optional, because that allows nodes to be copied from the optional
block to somewhere outside even though the optional could be disabled
later. When optionals are disabled the AST is reset and the
resolution is restarted at the point of resolving macro calls, so
anything resolved before macro calls will never be re-resolved.
This includes tunableifs, in-statements, blockinherits,
blockabstracts, and macro definitions. Tunable declarations also
cannot be in an optional block because they are needed to resolve
tunableifs. It should be fine to allow blockinherit statements in
an optional, because that is copying nodes from outside the optional
to the optional and if the optional is later disabled, everything
will be deleted anyway.
Check and quit with an error if a tunable declaration, in-statement,
block, blockabstract, or macro definition is found within an
optional when either building or resolving the AST.
Signed-off-by: James Carter <jwcart2@gmail.com>
When building the AST, typemember rules in a booleanif block will
be incorrectly called invalid. They are allowed in the kernel
policy and should be allowed in CIL.
When resolving the AST, if a neverallow rule is copied into a
booleanif block, it will not be considered an invalid rule, even
though this is not allowed in the kernel policy.
Update the booleanif checks to allow typemember rules and to not
allow neverallow rules in booleanifs. Also use the same form of
conditional for the checks when building and resolving the AST.
Signed-off-by: James Carter <jwcart2@gmail.com>
Reorder checks for invalid rules in the blocks of tunableifs,
in-statements, macros, and booleanifs when resolving the AST for
consistency.
Order the checks in the same order the blocks will be resolved in,
so tuanbleif, in-statement, macro, booleanif, and then non-block
rules.
Signed-off-by: James Carter <jwcart2@gmail.com>
When resolving the AST, block and optional stacks are used to
determine if the current rule being resolved is in a block or
an optional. There is no need to do this since the parent node
pointers can be used when exiting a block or an optional to
determine if resolution is still within a block or an optional.
When entering either a block or an optional, update the appropriate
tree node pointer. When finished with the last child of a block or
optional, set the appropriate pointer to NULL. If a parent of the
same kind is found when the parent node pointers are followed back
to the root node, then set the pointer to that tree node.
Signed-off-by: James Carter <jwcart2@gmail.com>
In order to find statements not allowed in tunableifs, in-statements,
macros, and booleanifs, there are tree node pointers that point to
each of these kinds of statements when its block is being parsed.
If the pointer is non-NULL, then the rule being parsed is in the block
of that kind of statement.
The tree node pointers were being updated at the wrong point which
prevented an invalid statement from being found if it was the first
statement in the block of a tunableif, in-statement, macro, or
booleanif.
Create a first child helper function for walking the parse tree and
in that function set the appropriate tree node pointer if the
current AST node is a tunableif, in-statement, macro, or booleanif.
This also makes the code symmetrical with the last child helper
where the tree node pointers are set to NULL.
Signed-off-by: James Carter <jwcart2@gmail.com>
Since parse_current, finished, and extra_args can never be NULL,
remove the useless check and directly assign local variables from
extra_args.
Signed-off-by: James Carter <jwcart2@gmail.com>
Reorder checks for invalid rules in the blocks of tunableifs,
in-statements, macros, and booleanifs when building the AST for
consistency.
Order the checks in the same order the blocks will be resolved in,
so tuanbleif, in-statement, macro, booleanif, and then non-block
rules.
Signed-off-by: James Carter <jwcart2@gmail.com>
In cil_gen_node(), after the declaration is added to the symbol
table, if the parent is a macro, then a check is made to ensure
the declaration does not shadow any of the macro's parameters.
This check also needs to be done when copying the AST.
Move the check for the shadowing of macro parameters to its own
function, cil_verify_decl_does_not_shadow_macro_parameter(), and
refactor cil_gen_node() and __cil_copy_node_helper() to use the
new function.
Signed-off-by: James Carter <jwcart2@gmail.com>
The functionality of adding a declaration to a symbol table is also
needed in __cil_copy_node_helper() and not just cil_gen_node().
Create a new function called cil_add_decl_to_symtab() to add a
declaration to a symtab and refactor cil_gen_node() and
__cil_copy_node_helper() to use the new function.
By using the new function, __cil_copy_node_helper() will now allow
duplicate declarations when appropriate.
Signed-off-by: James Carter <jwcart2@gmail.com>
Change the name of cil_is_datum_multiple_decl() to
cil_allow_multiple_decls() and make it static. The new function
takes the CIL db and the flavors of the old and new datum as
arguments. Also, put all of the logic of determining if multiple
declarations are allowed into the new function. Finally, update
the call from cil_gen_node().
Signed-off-by: James Carter <jwcart2@gmail.com>
The following policy will cause a segfault:
(class CLASS (PERM))
(class C (P1 P2 P3))
(classorder (CLASS C))
(sid SID)
(sidorder (SID))
(user USER)
(role ROLE)
(type TYPE)
(category CAT)
(categoryorder (CAT))
(sensitivity SENS)
(sensitivityorder (SENS))
(sensitivitycategory SENS (CAT))
(allow TYPE self (CLASS (PERM)))
(roletype ROLE TYPE)
(userrole USER ROLE)
(userlevel USER (SENS))
(userrange USER ((SENS)(SENS (CAT))))
(sidcontext SID (USER ROLE TYPE ((SENS)(SENS))))
(classmap CM (PM1 PM2 PM3))
(classmapping CM PM1 (C (P1)))
(classmapping CM PM2 (C (P2)))
(classmapping CM PM3 (C (P3)))
(allow TYPE self (CM (and (all) (not PM2))))
The problem is that, while permission expressions are allowed for
normal classes, map classes are expected to only have permission
lists and no check is done to verify that only a permission list
is being used.
When the above policy is parsed, the "and" and "all" are seen as
expression operators, but when the map permissions are converted to
normal class and permissions, the permission expression is assumed
to be a list of datums and since the operators are not datums a
segfault is the result.
There is no reason to limit map classes to only using a list of
permissions and, in fact, it would be better to be able to use them
in the same way normal classes are used.
Allow permissions expressions to be used for map classes by first
evaluating the permission expression and then converting the
resulting list to normal classes and permissions.
Signed-off-by: James Carter <jwcart2@gmail.com>
When CIL parses sets or conditional expressions, any identifier that
matches an operator name will always be taken as an operator. If a
declaration has the same name as an operator, then there is the
possibility of causing either confusion or a syntax error if it is
used in an expression. The potential for problems is much greater
than any possible advantage in allowing a declaration to share the
name of a reserved word.
Create a new function, __cil_is_reserved_name() that is called when
an identifier is declared and its name is being validated. In this
function, check if the declaration has the same name as a reserved
word for an expression operator that can be used with the identifer's
flavor and exit with an error if it does.
Also, move the check for types, type aliases, and type attributes
matching the reserved word "self" to this new function.
Finally, change the name of the function __cil_verify_name() to
cil_verify_name(), since this function is neither static nor a
helper function.
Signed-off-by: James Carter <jwcart2@gmail.com>
In constraint expressions u1, u3, r1, r3, t1, and t3 are never
allowed on the right side of an expression, but there were no checks
to verify that they were not used on the right side. The result was
that the expression "(eq t1 t1)" would be silently turned into
"(eq t1 t2)" when the binary policy was created.
Verify that u1, u3, r1, r3, t1, and t3 are not used on the right
side of a constraint expression.
Signed-off-by: James Carter <jwcart2@gmail.com>
The class field of a struct cil_classperms points to the class looked
up in the symbol table, so that field should be set to NULL when
the cil_classperms is reset.
Set the class field to NULL when resetting the struct cil_classperms.
Signed-off-by: James Carter <jwcart2@gmail.com>
In struct cil_classperms_set, the set field is a pointer to a
struct cil_classpermission which is looked up in the symbol table.
Since the cil_classperms_set does not create the cil_classpermission,
it should not reset it.
Set the set field to NULL instead of resetting the classpermission
that it points to.
Signed-off-by: James Carter <jwcart2@gmail.com>
Map perms share the same struct as regular perms, but only the
map perms use the classperms field. This field is a pointer to a
list of classperms that is created and added to when resolving
classmapping rules, so the map permission doesn't own any of the
data in the list and this list should be destroyed when the AST is
reset.
When resetting a perm, destroy the classperms list without destroying
the data in the list.
Signed-off-by: James Carter <jwcart2@gmail.com>
Nicolas Iooss reports:
A few months ago, OSS-Fuzz found a crash in the CIL compiler, which
got reported as
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28648 (the title
is misleading, or is caused by another issue that conflicts with the
one I report in this message). Here is a minimized CIL policy which
reproduces the issue:
(class CLASS (PERM))
(classorder (CLASS))
(sid SID)
(sidorder (SID))
(user USER)
(role ROLE)
(type TYPE)
(category CAT)
(categoryorder (CAT))
(sensitivity SENS)
(sensitivityorder (SENS))
(sensitivitycategory SENS (CAT))
(allow TYPE self (CLASS (PERM)))
(roletype ROLE TYPE)
(userrole USER ROLE)
(userlevel USER (SENS))
(userrange USER ((SENS)(SENS (CAT))))
(sidcontext SID (USER ROLE TYPE ((SENS)(SENS))))
(classpermission CLAPERM)
(optional OPT
(roletype nonexistingrole nonexistingtype)
(classpermissionset CLAPERM (CLASS (PERM)))
)
The CIL policy fuzzer (which mimics secilc built with clang Address
Sanitizer) reports:
==36541==ERROR: AddressSanitizer: heap-use-after-free on address
0x603000004f98 at pc 0x56445134c842 bp 0x7ffe2a256590 sp
0x7ffe2a256588
READ of size 8 at 0x603000004f98 thread T0
#0 0x56445134c841 in __cil_verify_classperms
/selinux/libsepol/src/../cil/src/cil_verify.c:1620:8
#1 0x56445134a43e in __cil_verify_classpermission
/selinux/libsepol/src/../cil/src/cil_verify.c:1650:9
#2 0x56445134a43e in __cil_pre_verify_helper
/selinux/libsepol/src/../cil/src/cil_verify.c:1715:8
#3 0x5644513225ac in cil_tree_walk_core
/selinux/libsepol/src/../cil/src/cil_tree.c:272:9
#4 0x564451322ab1 in cil_tree_walk
/selinux/libsepol/src/../cil/src/cil_tree.c:316:7
#5 0x5644513226af in cil_tree_walk_core
/selinux/libsepol/src/../cil/src/cil_tree.c:284:9
#6 0x564451322ab1 in cil_tree_walk
/selinux/libsepol/src/../cil/src/cil_tree.c:316:7
#7 0x5644512b88fd in cil_pre_verify
/selinux/libsepol/src/../cil/src/cil_post.c:2510:7
#8 0x5644512b88fd in cil_post_process
/selinux/libsepol/src/../cil/src/cil_post.c:2524:7
#9 0x5644511856ff in cil_compile
/selinux/libsepol/src/../cil/src/cil.c:564:7
The classperms list of a classpermission rule is created and filled
in when classpermissionset rules are processed, so it doesn't own any
part of the list and shouldn't retain any of it when it is reset.
Destroy the classperms list (without destroying the data in it) when
resetting a classpermission rule.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
Based on patch by Nicolas Iooss, who writes:
OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying
to compile the following policy:
(sid SID)
(sidorder(SID))
(filecon "\" any ())
(filecon "" any ())
When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
terminator of the string. Fix this by returning when '\0' is read
after a backslash.
To be consistent with the function compute_diffdata() in
refpolicy/support/fc_sort.py, also increment str_len in this case.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
In CIL, blocks, optionals, and macros share the same symbol table so
that the targets of "in" statements can be located. Because of this,
they cannot have the same name in the same namespace, but, because
they do not show up in the final policy, they can have the same name
as long as they are in different namespaces. Unfortunately, when
copying from one namespace to another, no check was being done to see
if there was a conflict.
When copying blocks, optionals, and macros, if a datum is found in
the destination namespace, then there is a conflict with a previously
declared block, optional, or macro, so exit with an error.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Reported-by: Evgeny Vereshchagin <evvers@ya.ru>
Signed-off-by: James Carter <jwcart2@gmail.com>
If a role or user attribute with nothing associated with it is used
in a constraint expression, then the bitmap will be empty. This is
not a problem for the kernel, but does cause problems when converting
a kernel policy or module to CIL.
When creating a CIL policy from a kernel policy or module, if an
empty bitmap is encountered, use the string "NO_IDENTIFIER". An
error will occur if an attempt is made to compile the resulting
policy, but a valid policy was not being produced before anyway.
Treat types the same way even though empty bitmaps are not expected.
Signed-off-by: James Carter <jwcart2@gmail.com>
When writing CIL policy from a kernel policy or module, if there are
multiple users, roles, or types, then the list needs to be enclosed
by "(" and ")".
When writing a constraint expression, check to see if there are
multiple identifiers in the names string and enclose the list with
"(" and ")" if there are.
Signed-off-by: James Carter <jwcart2@gmail.com>
The expectation in CIL was to use user, role, or type attributes in
constraint expressions. The problem is that neither user nor role
attributes are part of the kernel binary policy, so when converting
from a kernel policy to CIL, that would require the creation of a
role or user attribute. The better solution is to just allow a list
to be used. In fact, the only thing preventing a list to be used
is a check in cil_verify_constraint_leaf_expr_syntax().
Remove the check and allow lists in constraint expressions.
The following is now allowed:
(constrain (CLASS1 (PERM1)) (eq r1 (ROLE1 ROLE2 ROLE_ATTR3)))
Signed-off-by: James Carter <jwcart2@gmail.com>
When writing a policy.conf from a kernel policy, if there are
multiple users, roles, or types, then the list needs to be enclosed
by "{" and "}".
When writing a constraint expression, check to see if there are
multiple identifiers in the names string and enclose the list
with "{" and "}" if there are.
Signed-off-by: James Carter <jwcart2@gmail.com>
If a role attribute with no roles associated with it is used in a
constraint expression, then the role bitmap will be empty. This is
not a problem for the kernel, but does cause problems when
converting a kernel policy to policy.conf.
When creating a policy.conf from a kernel policy, if an empty bitmap
is encountered, use the string "NO_IDENTIFIER". An error will occur
if an attempt is made to compile the resulting policy, but this is
better than exiting with an error without creating a policy.conf.
Signed-off-by: James Carter <jwcart2@gmail.com>
Using signed integer to represent counts can troube some gcc
optimisation passes, for example in
https://github.com/fishilico/selinux/runs/2125501324?check_suite_focus=true#step:9:107
In function ‘name_list_to_string’,
inlined from ‘constraint_expr_to_string’ at module_to_cil.c:1799:11:
module_to_cil.c:1156:8: error: argument 1 range
[18446744071562067968, 18446744073709551615] exceeds maximum
object size 9223372036854775807 [-Werror=alloc-size-larger-than=]
1156 | str = malloc(len);
| ^~~~~~~~~~~
In file included from module_to_cil.c:39:
module_to_cil.c: In function ‘constraint_expr_to_string’:
/usr/include/stdlib.h:539:14: note: in a call to allocation
function ‘malloc’ declared here
539 | extern void *malloc (size_t __size) __THROW __attribute_malloc__
| ^~~~~~
The wide range (from 18446744071562067968 = 0xffffffff80000000 to
18446744073709551615 = 0xffffffffffffffff) was caused by num_names being
a signed int used in "len += num_names;", even though it should always
be non-negative.
Prevent such issues from occurring by using "unsigned int" where
appropriate.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a Null-dereference in __cil_insert_name when trying to
compile the following policy:
(macro MACRO ()
(classmap CLASS (PERM))
(type TYPE)
(typetransition TYPE TYPE CLASS "name" TYPE)
)
(call MACRO)
When using a macro with no argument, macro->params is NULL and
cil_list_for_each(item, macro->params) dereferenced a NULL pointer.
Fix this by checking that macro->params is not NULL before using it.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28565
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
All functions of the CIL compiler use cil_log or cil_tree_log to report
errors, but in two places which still uses printf. Replace these printf
invocation with cil_tree_log.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
printf("%i\n", node->flavor); looks very much like a statement which was
added for debugging purpose and was unintentionally left.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
cil_post_fc_fill_data() is not used outside of cil_post.c, and is not
exported in libsepol.so. Make it static, in order to ease the analysis
of static analyzers.
While at it, make its path argument "const char*" and the fields of
"struct fc_data" "unsigned int" or "size_t", in order to make the types
better match the values.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
For policy versions between 20 and 23, attributes exist in the
policy, but only in the type_attr_map. This means that there are
gaps in both the type_val_to_struct and p_type_val_to_name arrays
and policy rules can refer to those gaps which can lead to NULL
dereferences when using sepol_kernel_policydb_to_conf() and
sepol_kernel_policydb_to_cil().
This can be seen with the following policy:
class CLASS1
sid SID1
class CLASS1 { PERM1 }
attribute TYPE_ATTR1;
type TYPE1;
typeattribute TYPE1 TYPE_ATTR1;
allow TYPE_ATTR1 self : CLASS1 PERM1;
role ROLE1;
role ROLE1 types TYPE1;
user USER1 roles ROLE1;
sid SID1 USER1:ROLE1:TYPE1
Compile the policy:
checkpolicy -c 23 -o policy.bin policy.conf
Converting back to a policy.conf causes a segfault:
checkpolicy -F -b -o policy.bin.conf policy.bin
Have both sepol_kernel_policydb_to_conf() and
sepol_kernel_policydb_to_cil() exit with an error if the kernel
policy version is between 20 and 23.
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
At one point link_modules() might have needed this initial copying,
but now it serves no purpose, so remove it.
Signed-off-by: James Carter <jwcart2@gmail.com>
Types associated to role attributes in optional blocks are not
associated with the roles that have that attribute. The problem
is that role_fix_callback is called before the avrule_decls are
walked.
Example/
class CLASS1
sid kernel
class CLASS1 { PERM1 }
type TYPE1;
type TYPE1A;
allow TYPE1 self : CLASS1 PERM1;
attribute_role ROLE_ATTR1A;
role ROLE1;
role ROLE1A;
roleattribute ROLE1A ROLE_ATTR1A;
role ROLE1 types TYPE1;
optional {
require {
class CLASS1 PERM1;
}
role ROLE_ATTR1A types TYPE1A;
}
user USER1 roles ROLE1;
sid kernel USER1:ROLE1:TYPE1
In this example ROLE1A will not have TYPE1A associated to it.
Call role_fix_callback() after the avrule_decls are walked.
Signed-off-by: James Carter <jwcart2@gmail.com>
When creating the kernel binary policy, role attributes in constraint
expressions are not expanded. This causes the constraint expression
to refer to a non-existent role in the kernel policy. This can lead
to a segfault when converting the binary policy back to conf or CIL
source or when using policy tools such as seinfo.
Expand role attributes in constraint expressions when creating the
kernel binary policy.
Reported-by: Christian Göttsche <cgzones@googlemail.com>
Signed-off-by: James Carter <jwcart2@gmail.com>
Facebook's Infer static analyzer warns about a use-after-free issue in
libsemanage:
int semanage_direct_mls_enabled(semanage_handle_t * sh)
{
sepol_policydb_t *p = NULL;
int retval;
retval = sepol_policydb_create(&p);
if (retval < 0)
goto cleanup;
/* ... */
cleanup:
sepol_policydb_free(p);
return retval;
}
When sepol_policydb_create() is called, p is allocated and
policydb_init() is called. If this second call fails, p is freed
andsepol_policydb_create() returns -1, but p still stores a pointer to
freed memory. This pointer is then freed again in the cleanup part of
semanage_direct_mls_enabled().
Fix this by setting p to NULL in sepol_policydb_create() after freeing
it.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
CIL permits not assigning a context to a SID, e.g. to an unused initial
SID, e.g. 'any_socket'.
When using the example policy from the SELinux Notebook,
https://github.com/SELinuxProject/selinux-notebook/blob/main/src/notebook-examples/cil-policy/cil-policy.cil,
secilc logs:
No context assigned to SID any_socket, omitting from policy at cil-policy.cil:166
But secil2conf segfaults when writing the policy.conf:
../cil/src/cil_policy.c:274:2: runtime error: member access within null pointer of type 'struct cil_context'
Only print the sid context statement if a context was actually assigned.
The sid declaration is still included via cil_sid_decls_to_policy().
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
Nicolas Iooss reports that fuzzing /usr/libexec/hll/pp with the
American Fuzzy Lop revealed that inconsistent policy modules could be
created that caused NULL dereferences and other problems.
When reading in a binary modular or kernel policy, check values in the
policydb to verify consistency. When reading in the data for commons,
classes, roles, types, users, booleans, sensitivities, and categories
verify that their value is between 1 and the number of primary
identifiers (value-1 is used to index the sym_val_to_name array for
all of these and the val_to_struct array for classes, roles, users,
and types.) Next all references in policy rules are checked to ensure
that they refer to a valid value.
It is possible for the type and role struct and name arrays to have
gaps in them. For roles, there will be gaps in the case of a kernel
policy created from a policy with role attributes, but nothing in the
policy will refer to any of the gaps. For types, there will be gaps
for any kernel policy with a version from 20 to 23, but, unfortunately,
there will be references to the gaps. This is because, while attributes
exist in these policies, they only exist in the type_attr_map. For
policies with versions between 20 and 23, it must be assumed that all
of the gaps and any references to them are valid. To check for
references to gaps, bitmaps are created to map where the gaps are and
all values are verified to be within the proper range and not within a
gap.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
Create the function ebitmap_highest_set_bit() which returns the position
of the highest bit set in the ebitmap.
The return value is valid only if the ebitmap is not empty. An empty
ebitmap will return 0.
Signed-off-by: James Carter <jwcart2@gmail.com>
Nicolas Iooss reports:
I am continuing to investigate OSS-Fuzz crashes and the following one
is quite complex. Here is a CIL policy which triggers a
heap-use-after-free error in the CIL compiler:
(class CLASS (PERM2))
(classorder (CLASS))
(classpermission CLSPRM)
(optional o
(mlsvalidatetrans x (domby l1 h1))
(common CLSCOMMON (PERM1))
(classcommon CLASS CLSCOMMON)
)
(classpermissionset CLSPRM (CLASS (PERM1)))
The issue is that the mlsvalidatetrans fails to resolve in pass
CIL_PASS_MISC3, which comes after the resolution of classcommon (in
pass CIL_PASS_MISC2). So:
* In pass CIL_PASS_MISC2, the optional block still exists, the
classcommon is resolved and class CLASS is linked with common
CLSCOMMON.
* In pass CIL_PASS_MISC3, the optional block is destroyed, including
the common CLSCOMMON.
* When classpermissionset is resolved, function cil_resolve_classperms
uses "common_symtab = &class->common->perms;", which has been freed.
The use-after-free issue occurs in __cil_resolve_perms (in
libsepol/cil/src/cil_resolve_ast.c):
// common_symtab was freed
rc = cil_symtab_get_datum(common_symtab, curr->data, &perm_datum);
The fundamental problem here is that when the optional block is
disabled it is immediately destroyed in the middle of the pass, so
the class has not been reset and still refers to the now destroyed
common when the classpermissionset is resolved later in the same pass.
Added a list, disabled_optionals, to struct cil_args_resolve which is
passed when resolving the tree. When optionals are disabled, they are
now added to this list and then are destroyed after the tree has been
reset between passes.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
clang 11.0.0 reports the following warning several times (when building
with "make CC=clang" from libsepol directory, in the default
configuration of the git repository):
../cil/src/cil_binary.c:1980:8: error: cast to smaller integer type
'enum cil_flavor' from 'void *' [-Werror,-Wvoid-pointer-to-enum-cast]
op = (enum cil_flavor)curr->data;
^~~~~~~~~~~~~~~~~~~~~~~~~~~
Silence this warning by casting the pointer to an integer the cast to
enum cil_flavor.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a Null-dereference READ in the CIL compiler when trying
to compile the following policy:
(<src_info>)
In cil_gen_src_info(), parse_current->next is NULL even though the code
expects that both parse_current->next and parse_current->next->next
exists.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28457
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a Null-dereference READ in the CIL compiler when trying
to compile the following policy:
(macro m((name n))) (call m(()))
When calling the macro, the name (in variable "pc") is NULL, which
triggers a NULL pointer dereference when using it as a key in
__cil_insert_name(). The stack trace is:
#0 0x7f4662655a85 in __strlen_avx2 (/usr/lib/libc.so.6+0x162a85)
#1 0x556d0b6d150c in __interceptor_strlen.part.0 (/selinux/libsepol/fuzz/fuzz-secilc+0x44850c)
#2 0x556d0ba74ed6 in symhash /selinux/libsepol/src/symtab.c:22:9
#3 0x556d0b9ef50d in hashtab_search /selinux/libsepol/src/hashtab.c:186:11
#4 0x556d0b928e1f in cil_symtab_get_datum /selinux/libsepol/src/../cil/src/cil_symtab.c:121:37
#5 0x556d0b8f28f4 in __cil_insert_name /selinux/libsepol/src/../cil/src/cil_resolve_ast.c:96:2
#6 0x556d0b908184 in cil_resolve_call1 /selinux/libsepol/src/../cil/src/cil_resolve_ast.c:2835:12
#7 0x556d0b91b404 in __cil_resolve_ast_node /selinux/libsepol/src/../cil/src/cil_resolve_ast.c
#8 0x556d0b91380f in __cil_resolve_ast_node_helper /selinux/libsepol/src/../cil/src/cil_resolve_ast.c:3773:7
#9 0x556d0b932230 in cil_tree_walk_core /selinux/libsepol/src/../cil/src/cil_tree.c:263:9
#10 0x556d0b932230 in cil_tree_walk /selinux/libsepol/src/../cil/src/cil_tree.c:307:7
#11 0x556d0b932326 in cil_tree_walk_core /selinux/libsepol/src/../cil/src/cil_tree.c:275:9
#12 0x556d0b932326 in cil_tree_walk /selinux/libsepol/src/../cil/src/cil_tree.c:307:7
#13 0x556d0b911189 in cil_resolve_ast /selinux/libsepol/src/../cil/src/cil_resolve_ast.c:3941:8
#14 0x556d0b798729 in cil_compile /selinux/libsepol/src/../cil/src/cil.c:550:7
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28544
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Nicolas Iooss reports:
OSS-Fuzz found an integer overflow when compiling the following
(empty) CIL policy:
;;*lms 2147483647 a
; (empty line)
Change hll_lineno to uint32_t which is the type of the field hll_line
in struct cil_tree_node where the line number will be stored eventually.
Read the line number into an unsigned long variable using strtoul()
instead of strtol(). On systems where ULONG_MAX > UINT32_MAX, return
an error if the value is larger than UINT32_MAX.
Also change hll_expand to uint32_t, since its value will be either
0 or 1 and there is no need for it to be signed.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
It is good practise in C to include the header file that specifies the
prototype of functions which are defined in the source file. Otherwise,
the function prototypes which be different, which could cause unexpected
issues.
Add the include directives to do this.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
In libsepol/src/mls.c, functions sepol_mls_contains and sepol_mls_check
used "sepol_policydb_t * policydb" even though
libsepol/include/sepol/context.h used "const sepol_policydb_t *
policydb".
Add const qualifiers in mls.c in order to match the header file. Detect
such mismatching error at compile time by including the header file in
mls.c.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
libsepol/src/roles.c contains functions which do not match its header
file libsepol/include/sepol/roles.h:
// In roles.c
int sepol_role_exists(sepol_handle_t * handle __attribute__ ((unused)),
sepol_policydb_t * p, const char *role, int *response)
// In roles.h
extern int sepol_role_exists(const sepol_policydb_t * policydb,
const char *role, int *response);
and:
// In roles.c
int sepol_role_list(sepol_handle_t * handle,
sepol_policydb_t * p, char ***roles, unsigned int *nroles)
// In roles.h
extern int sepol_role_list(const sepol_policydb_t * policydb,
char ***roles, unsigned int *nroles);
Instead of fixing the parameter type (using sepol_handle_t or
sepol_policydb_t but not different ones), remove these functions, as
they appear not to be used. They are not exported in libsepol.so.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
This is based on a patch by Nicolas Iooss. He writes:
When secilc compiles the following policy:
(block b1
(optional o1
(blockinherit b1)
(blockinherit x)
)
)
it disables the optional block at pass 3 (CIL_PASS_BLKIN_LINK)
because the block "x" does not exist.
__cil_resolve_ast_last_child_helper() calls
cil_tree_children_destroy() on the optional block, which destroys
the two blockinherit statements. But the (blockinherit b1) node
was referenced inside (block b1) node, in its block->bi_nodes list.
Therefore, when this list is used at pass 4 (CIL_PASS_BLKIN_COPY),
it contains a node which was freed: this triggers a use-after-free
issue
Fix this issue by removing blockinherit nodes from their lists of
nodes block->bi_nodes when they are being destroyed. As
cil_destroy_blockinherit() does not have a reference to the node
containing the blockinherit data, implement this new logic in
cil_tree_node_destroy().
This issue was found while investigating a testcase from an OSS-Fuzz
issue which seems unrelated (a Null-dereference READ in
cil_symtab_get_datum,
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29861).
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
The following CIL policy triggers a heap use-after-free in secilc
because when the blockinherit node is destroyed, the block node was
already destroyed:
(block b2a)
(blockinherit b2a)
Fix this by setting blockinherit->block to NULL when destroying block.
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Acked-by: James Carter <jwcart2@gmail.com>
When __cil_validate_constrain_expr() fails,
cil_constrain_to_policydb_helper() does not destroy the constraint
expression. This leads to a memory leak reported by OSS-Fuzz with the
following CIL policy:
(class CLASS (PERM))
(classorder (CLASS))
(sid SID)
(sidorder (SID))
(user USER)
(role ROLE)
(type TYPE)
(category CAT)
(categoryorder (CAT))
(sensitivity SENS)
(sensitivityorder (SENS))
(sensitivitycategory SENS (CAT))
(allow TYPE self (CLASS (PERM)))
(roletype ROLE TYPE)
(userrole USER ROLE)
(userlevel USER (SENS))
(userrange USER ((SENS)(SENS (CAT))))
(sidcontext SID (USER ROLE TYPE ((SENS)(SENS))))
(constrain
(CLASS (PERM))
(or
(eq t1 TYPE)
(or
(eq t1 TYPE)
(or
(eq t1 TYPE)
(or
(eq t1 TYPE)
(or
(eq t1 TYPE)
(eq t1 TYPE)
)
)
)
)
)
)
Add constraint_expr_destroy(sepol_expr) to destroy the expression.
Moreover constraint_expr_destroy() was not freeing all items of an
expression. Code in libsepol/src and checkpolicy contained while loop to
free all the items of a constraint expression, but not the one in
libsepol/cil. As freeing only the first item of an expression is
misleading, change the semantic of constraint_expr_destroy() to iterate
the list of constraint_expr_t and to free all items.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28938
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Acked-by: James Carter <jwcart2@gmail.com>
Nicolas Iooss reports:
A few weeks ago, OSS-Fuzz got configured in order to fuzz the CIL
policy compiler (cf.
https://github.com/SELinuxProject/selinux/issues/215 and
https://github.com/google/oss-fuzz/pull/4790). It reported a bunch of
simple issues, for which I will submit patches. There are also more
subtle bugs, like the one triggered by this CIL policy:
(class CLASS (PERM))
(classorder (CLASS))
(sid SID)
(sidorder (SID))
(sensitivity SENS)
(sensitivityorder (SENS))
(type TYPE)
(allow TYPE self (CLASS (PERM)))
(block b
(optional o
(sensitivitycategory SENS (C)) ; Non-existing category
triggers disabling the optional
(common COMMON (PERM1))
(classcommon CLASS COMMON)
(allow TYPE self (CLASS (PERM1)))
)
)
On my computer, secilc manages to build this policy fine, but when
clang's Address Sanitizer is enabled, running secilc leads to the
following report:
$ make -C libsepol/src CC=clang CFLAGS='-g -fsanitize=address' libsepol.a
$ clang -g -fsanitize=address secilc/secilc.c libsepol/src/libsepol.a
-o my_secilc
$ ./my_secilc -vv testcase.cil
Parsing testcase.cil
Building AST from Parse Tree
Destroying Parse Tree
Resolving AST
Failed to resolve sensitivitycategory statement at testcase.cil:12
Disabling optional 'o' at testcase.cil:11
Resetting declarations
=================================================================
==181743==ERROR: AddressSanitizer: heap-use-after-free on address
0x6070000000c0 at pc 0x55ff7e445d24 bp 0x7ffe7eecfba0 sp
0x7ffe7eecfb98
READ of size 4 at 0x6070000000c0 thread T0
#0 0x55ff7e445d23 in __class_reset_perm_values
/git/selinux-userspace/libsepol/src/../cil/src/cil_reset_ast.c:17:17
The problem is that the optional branch is destroyed when it is disabled,
so the common has already been destroyed when the reset code tries to
access the number of common permissions, so that it can change the
value of the class permissions back to their original values.
The solution is to count the number of class permissions and then
calculate the number of common permissions.
Reported-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: James Carter <jwcart2@gmail.com>
This field is suppose to be used to track the number of primary names in
the symtab. It was not being updated or used.
Increment the nprim field when a new datum is added to the symtab and
decrement the field when a datum is removed.
Signed-off-by: James Carter <jwcart2@gmail.com>
OSS-Fuzz found a direct memory leak in policydb_filetrans_insert()
because filenametr_destroy() does not fully destroy the list associated
with a typetransition.
More precisely, let's consider this (minimized) CIL policy:
(class CLASS (PERM))
(classorder (CLASS))
(sid SID)
(sidorder (SID))
(user USER)
(role ROLE)
(type TYPE) ; "type 1" in libsepol internal structures
(type TYPE2) ; "type 2" in libsepol internal structures
(type TYPE3) ; "type 3" in libsepol internal structures
(category CAT)
(categoryorder (CAT))
(sensitivity SENS)
(sensitivityorder (SENS))
(sensitivitycategory SENS (CAT))
(allow TYPE self (CLASS (PERM)))
(roletype ROLE TYPE)
(userrole USER ROLE)
(userlevel USER (SENS))
(userrange USER ((SENS)(SENS (CAT))))
(sidcontext SID (USER ROLE TYPE ((SENS)(SENS))))
(typetransition TYPE2 TYPE CLASS "some_file" TYPE2)
(typetransition TYPE3 TYPE CLASS "some_file" TYPE3)
The two typetransition statements make policydb_filetrans_insert()
insert an item with key {ttype=1, tclass=1, name="some_file"} in the
hashmap p->filename_trans. This item contains a linked list of two
filename_trans_datum_t elements:
* The first one uses {otype=2, stypes=bitmap containing 2}
* The second one uses {otype=3, stypes=bitmap containing 3}
Nevertheless filenametr_destroy() (called by
hashtab_map(p->filename_trans, filenametr_destroy, NULL);) only frees
the first element. Fix this memory leak by freeing all elements.
This issue was introduced by commit 42ae834a74 ("libsepol,checkpolicy:
optimize storage of filename transitions") and was never present in the
kernel, as filenametr_destroy() was modified appropriately in commit
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c3a276111ea2572399281988b3129683e2a6b60b
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29138
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Acked-by: Ondrej Mosnacek <omosnace@redhat.com>
OSS-Fuzz found a NULL pointer dereference when the CIL compiler tries to
compile a policy with an invalid integer:
$ echo '(ioportcon(2())n)' > tmp.cil
$ secilc tmp.cil
Segmentation fault (core dumped)
This is because strtol() is called with a NULL pointer, in
cil_fill_integer().
Fix this by checking that int_node->data is not NULL. While at it, use
strtoul() instead of strtol() to parse an unsigned integer.
When using "val > UINT32_MAX" with "unsigned long val;", it is expected
that some compilers emit a warning when the size of "unsigned long" is
32 bits. In theory gcc could be such a compiler (with warning
-Wtype-limits, which is included in -Wextra). Nevertheless this is
currently broken, according to
https://gcc.gnu.org/pipermail/gcc-help/2021-January/139755.html and
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89126 (this bug was
opened in January 2019).
In order to prevent this warning from appearing, introduce some
preprocessor macros around the bound check.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28456
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Acked-by: James Carter <jwcart2@gmail.com>
When __cil_resolve_perms fails, it does not destroy perm_datums, which
leads to a memory leak reported by OSS-Fuzz with the following CIL
policy:
(class cl01())
(classorder(cl01))
(type at02)
(type tpr3)
(allow at02 tpr3(cl01((s))))
Calling cil_list_destroy() fixes the issue.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28466
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a heap buffer overflow (out-of-bound reads) when the CIL
compiler tries to report a recursive blockinherit with an optional
block:
$ echo '(block b (optional o (blockinherit b)))' > tmp.cil
$ secilc tmp.cil
Segmentation fault (core dumped)
This is because cil_print_recursive_blockinherit() assumes that all
nodes are either CIL_BLOCK or CIL_BLOCKINHERIT. Add support for other
block kinds, using cil_node_to_string() to show them.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28462
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Function cil_add_file() copies its input into a newly-allocated buffer,
and does not modify "name". State these properties in the types of
parameters by adding "const" qualifiers.
This enables using LibFuzzer directly on cil_add_file(), without a
warning about discarding "const" qualifier:
fuzz-secilc.c: In function ‘LLVMFuzzerTestOneInput’:
fuzz-secilc.c:57:31: warning: passing argument 3 of ‘cil_add_file’
discards ‘const’ qualifier from pointer target type
[-Wdiscarded-qualifiers]
57 | if (cil_add_file(db, "fuzz", data, size) != SEPOL_OK)
| ^~~~
In file included from fuzz-secilc.c:26:
/usr/include/sepol/cil/cil.h:45:57: note: expected ‘char *’ but
argument is of type ‘const uint8_t *’ {aka ‘const unsigned char *’}
45 | extern int cil_add_file(cil_db_t *db, char *name, char *data, size_t size);
| ~~~~~~^~~~
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a Null-dereference READ in the CIL compiler when trying
to compile the following policy:
(optional o (validatetrans x (eq t3 (a ()))))
With some logs, secilc reports:
Invalid syntax
Destroying Parse Tree
Resolving AST
Failed to resolve validatetrans statement at fuzz:1
Disabling optional 'o' at tmp.cil:1
So there is an "Invalid syntax" error, but the compilation continues.
Fix this issue by stopping the compilation when cil_fill_list() reports
an error:
Invalid syntax
Bad expression tree for constraint
Bad validatetrans declaration at tmp.cil:1
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=29061
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
OSS-Fuzz found a heap use-after-free when the CIL compiler destroys its
database after failing to compile the following policy:
(validatetrans x (eq t3 (a)))
This is caused by the fact that the validatetrans AST object references
a stack variable local to __cil_fill_constraint_leaf_expr, when parsing
the list "(a)":
struct cil_list *sub_list;
cil_fill_list(current->next->next->cl_head, leaf_expr_flavor, &sub_list);
cil_list_append(*leaf_expr, CIL_LIST, &sub_list);
Drop the & sign to really add the list like it is supposed to be.
Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28507
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>