The Path Auditor is a tool meant to find file access related vulnerabilities by auditing libc functions.

Path Auditor idea is roughly as follows:

  • Audit every call to filesystem related libc functions performed by the binary.
  • Check if the path used in the syscall is user-writable. In this case an unprivileged user could have replaced a directory or file with a symlink.
  • Log all violations as potential vulnerabilities.

We’re using LD_PRELOAD to hook all filesystem related library calls and log any encountered violations to syslog.

This is not an officially supported Google product.

Also Read – NodeCrypto : Linux Ransomware Written In NodeJs

Example Vulnerability

Let’s look at an example of the kind of vulnerability that this tool can detect. CVE-2019-3461 was a bug in tmpreaper, a tool that traverses /tmp and deletes old files. It’s usually run as a cron job as root. Since it doesn’t want to delete files outside of tmp, it was using the following code to check if a directory is a mount point:

if (S_ISDIR (sb.st_mode)) {
char *dst;
if ((dst = malloc(strlen(ent->d_name) + 3)) == NULL)
message (LOG_FATAL, “malloc failed.\n”);
strcpy(dst, ent->d_name);
strcat(dst, “/X”);
rename(ent->d_name, dst);
if (errno == EXDEV) {
free(dst);
message (LOG_VERBOSE,
“File on different device skipped: `%s/%s’\n”,
dirname, ent->d_name);
continue;
}
// […]

In short, this code calls rename("/tmp/foo", "/tmp/foo/x") which will return EXDEV if "/tmp/foo" is a mount point. PathAuditor would flag this call as a potential vulnerability if "/tmp/foo" is owned by any user except root. To understand why, we have to think about what happens in the kernel when the rename syscall is executed (simplified):

  • The kernel traverses the path "/tmp/foo" for the first argument.
  • The kernel traverses the path "/tmp/foo/x" for the second argument.
  • If the source and target are on different filesystems, return EXDEV.
  • Otherwise, move the file from the first to the second directory.

There’s a race condition here since "/tmp/foo" will be resolved twice. If it’s user-controlled, the user can replace it with a different file at any point in time. In particular, we want "/tmp/foo" to be a directory at first to pass the if(S_ISDIR) check in the tmpreaper code. We then replace it with a file just before the code enters the syscall. When the kernel resolves the first argument, it will see a file with user-controlled content. Now we replace it again, this time with a symlink to an arbitrary directory on the same filesystem. The kernel will resolve the path a second time, follow the symlink and move the controlled file to a chosen directory.

The same filesystem restriction is because rename does not work between filesystems. But on some Linux distributions /tmp is just a folder on the rootfs by default and you could use this bug to move a file to /etc/cron, which will get executed as root.

How to run?

To try it out, you need to build libpath_auditor.so with bazel and load it into a binary using LD_PRELOAD. Any violations will be logged to syslog, so make sure that you have it running.

>>bazel build //patauditor/libc:libpath_auditor.so
>> LD_PRELOAD=/path/to/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
>>tail /var/log/syslog

It’s also possible to run this on all processes on the system by adding it to /etc/ld.so.preload. Though be warned that this is only recommended on test systems as it can lead to instability.

As a quickstart, you can try out the docker container shipped with this project:

docker build -t pathauditor-example .
docker run -it pathauditor-example
# LD_PRELOAD=/pathauditor/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
# cat /var/log/syslog