1

I have to prevent the #include of any files out of system directories because of security reasons. Is there any restrictions that can prevent #include<...> and #include"..."from including unsafe files like #include </dev/tty> or #include "/dev/random"?

I have read the header files chapter of Docs of C Preprocessor as well as a similar question How do I prevent a quoted include from searching the directory of the current source file? but can't find a proper way to do so.

Since -I- command is obsolete and there is no other way that can accomplish the same function, can I use jailkit to compile the code as a low-privilege user? Or is there other way to ensure the security of compile process?

Community
  • 1
  • 1
Fissure Blue
  • 181
  • 1
  • 7
  • 8
    This doesn't seem like an effective way to achieve security. If you're allowing users to compile their own code, forcing them to put malicious code in the source files instead of the header files won't help much. – Dmitri Sep 01 '16 at 17:14
  • I have traced the system call of user's program when running it and it will be terminated if dangerous system calls are detected. What I'm going to do is to ensure no other files will be included when compiling user's code. – Fissure Blue Sep 01 '16 at 17:21
  • You might want to review [How do I run the preprocessor on local headers only?](http://stackoverflow.com/questions/20889460/) to see that simply controlling what's in the `#include` lines in no way protects you from including files you didn't intend to be included. – Jonathan Leffler Sep 01 '16 at 17:23
  • 2
    There's nothing in *any* header file that couldn't be copied directly into the source code... – Dmitri Sep 01 '16 at 17:23
  • If you give others access to the source code, and the ability to build your program, there's nothing stopping them from doing whatever they want. If you set flags to disallow certain includes, then what's to stop the others to change the `Makefile` with the flags? – Some programmer dude Sep 01 '16 at 17:26
  • 1
    Even if you can guard against these includes, the compiler is not designed with security in mind. – Daniel Sep 01 '16 at 17:29
  • Can you explain how you are planning to prevent someone from copying /usr/include/stdio.h into their current directory, and then simply including stdio.h from their current directory? – Sam Varshavchik Sep 01 '16 at 17:32
  • I won't give users access to the compile system nor the building process, I just receive their code and return the result of it, so I need to do something to prevent malicious code when compiling and do system call monitoring.. – Fissure Blue Sep 01 '16 at 17:35
  • User cannot access my system, they can only submit their code. All compile and running process are designed and accomplished by me. – Fissure Blue Sep 01 '16 at 17:37
  • You don't need any `#include` directives at all to cause the preprocessor to expand a modest-size source file to an enormous preprocessed result. – John Bollinger Sep 01 '16 at 17:37
  • If you want to limit the compile processes with respect to which system files they can read, however, then that seems like a job for your system security mechanisms. SELinux, for example. – John Bollinger Sep 01 '16 at 17:39
  • If you already have the technology to prevent the user's compiled program from opening files you don't want opened, maybe you can use the same technology to monitor the compiler. – Mark Plotnick Sep 01 '16 at 17:58
  • I just prevent the open function of compiled program, with stdin as input, but compiler cannot be done the same way cuz it need access to legal headers, even I can monitor the compiler, I need a white-list of legal header files, that's a complicated list I think. @Plotnick – Fissure Blue Sep 01 '16 at 18:04
  • include -nostdlib in your make file , it will prevent compiler from using all standard libraries ... – sanjeev Sep 01 '16 at 18:08
  • @sanjeev Not only libraries, user's code may include files like /dev/random as shown above. Including these files may cause a long compiling time or crash. – Fissure Blue Sep 01 '16 at 18:13
  • Based on these comments, I would suggest that your problem is only partially an "#include" file problem. After all, a user could copy the contents of an include file that is compatible with your system and include it in their files in a way that would be very difficult to detect. Okay, so maybe it's a linker problem. You might want to enforce a custom linker script that prevents linking against certain OS functions. – Peter M Sep 01 '16 at 20:17
  • But the linker is just finding conveniently pre-compiled libraries - a user could also include the sources of these functions within their source, and rename everything to make it difficult to pattern match or do anything useful. Ultimately, what you want to do is just have this program execute in a sandbox, where the user has no access to system calls or files. That's a user privilege problem, not a compiler or #include problem. – Peter M Sep 01 '16 at 20:18
  • That's presumably what godbolt.org does, the only reason they run sources though a script to detect system paths is as a courtesy to their users. – Peter M Sep 01 '16 at 20:21

1 Answers1

3

godbolt.org solves a similar problem by forbidding absolute and relative paths in #includes:

Input:

#include "/etc/passwd"

Output:

<stdin>:1:1: no absolute or relative includes please
Compilation failed

This can be achieved by running a simple checker on the sources, before invoking the compiler. The checker only has to grep for #include directives and check the paths against violations of such restrictions. A draft version of such a script (usable with a single source file) follows:

check_includes:

#!/bin/bash

if (( $# != 1 ))
then
    echo "Usage: $(basename "$0") source_file"
    exit 1
fi

join_lines()
{
    sed '/\\$/ {N;s/\\\n//;s/^/\n/;D}' "$1"
}

absolute_includes()
{
    join_lines "$1"|grep '^\s*#\s*include\s*["<]\s*/'
}

relative_includes()
{
    join_lines "$1"|grep '^\s*#\s*include'|fgrep '../'
}

includes_via_defines()
{
    join_lines "$1"|grep '^\s*#\s*include\s*[^"< \t]'
}

show_and_count()
{
    tee /dev/stderr|wc -l
}

exit_status=0
if (( 0 != $(absolute_includes "$1"|show_and_count) ))
then
    echo 1>&2 "ERROR: $1 contains absolute includes"
    exit_status=1
fi
if (( 0 != $(relative_includes "$1"|show_and_count) ))
then
    echo 1>&2 "ERROR: $1 contains relative includes"
    exit_status=1
fi
if (( 0 != $(includes_via_defines "$1"|show_and_count) ))
then
    echo 1>&2 "ERROR: $1 contains includes via defines"
    exit_status=1
fi
exit $exit_status

Assuming that the input file can be compiled without errors, this script identifies #includes that contain absolute or relative paths. It also detects includes done via a macro like below (this can be abused to work around the path checking):

#define HEADER "/etc/password"
#include HEADER
Leon
  • 31,443
  • 4
  • 72
  • 97
  • This may be the simplest way to do so, but the implement seems complicated. Is there any open-source project? – Fissure Blue Sep 01 '16 at 17:47
  • @FissureBlue I think that the checker can be implemented as a simple bash script. I will post shortly a draft version that works for a single file. – Leon Sep 01 '16 at 17:51
  • What about a strange presentation of code? Maybe it need a regular expressions check? – Fissure Blue Sep 01 '16 at 18:07
  • @FissureBlue What kind of *strange presentation of code*? – Leon Sep 01 '16 at 18:45
  • I don't think this is enough: C allows `#define HEADER "/etc/password" #include HEADER`. – user58697 Sep 01 '16 at 19:46
  • Yeah, a `#define` of headers can escape the check shell. – Fissure Blue Sep 02 '16 at 01:21
  • @FissureBlue Fixed. `#include`s via macros are forbidden too (even if the macro expands to an otherwise valid path). – Leon Sep 02 '16 at 04:32
  • Then there's comments: `#include /* */ `. Possibly with a newline in the comment. Possibly with trigraphs, forming backslash newline sequences in the middle of the `/` and the `*` (or the `*` and the `/`). People can get very inventive! – Jonathan Leffler Sep 02 '16 at 04:44
  • @JonathanLeffler Those won't pass the check either (though the error message will be an incorrect one). The checker requires that `#include` be followed by `"` or `<` (after joining lines broken with backslash+\n sequences). – Leon Sep 02 '16 at 04:54
  • OK; it took me a while to work out what was happening, but your script does rule them out, albeit with an incorrect diagnosis. It allows through 'relative paths' of the forms `` and `"openssl/openssl.h"`, which is mostly sensible — but a lot depends on what sort of environment people are allowed to create for compiling their code. Your 'relative path' option excludes lines such as `#include "../somewhere/something.h"` which is OK. – Jonathan Leffler Sep 02 '16 at 05:13
  • Thanks a lot. I use ACL and your script to solve this problem at last. – Fissure Blue Sep 02 '16 at 08:27