-
Notifications
You must be signed in to change notification settings - Fork 231
auditd: support loading plugin configs from symlinks #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Hello, |
|
audit-userspace/audisp/audispd-pconfig.c Lines 182 to 187 in 263414b
load_pconfig itself actually checks the file mode, and the open does not take O_NOFOLLOW, so we probably could use if (e->d_type != DT_REG && e->d_type != DT_LNK) - or ignore that check completely, considering there is TOCTOU weirdness here anyways (check happens on the direntry, but later the name is actually opened and rechecked, the actual file might have changed since).
I see three options:
I tried to keep behavior the same, but this might not be the best approach. Of the three options, tell me which you prefer and i'll update this PR accordingly. |
|
I just ran both of the alternative candidates through my VM tests: diff --git a/audisp/audispd.c b/audisp/audispd.c
index 2113d4f9..871e0aad 100644
--- a/audisp/audispd.c
+++ b/audisp/audispd.c
@@ -118,7 +118,7 @@ static void load_plugin_conf(conf_llist *plugin)
char fname[PATH_MAX];
const char *ext, *reason = NULL;
- if (e->d_type != DT_REG)
+ if (e->d_type != DT_REG && e->d_type != DT_LNK)
reason = "not a regular file";
else if (e->d_name[0] == '.')
reason = "hidden file";diff --git a/audisp/audispd.c b/audisp/audispd.c
index 2113d4f9..70a8a298 100644
--- a/audisp/audispd.c
+++ b/audisp/audispd.c
@@ -118,9 +118,7 @@ static void load_plugin_conf(conf_llist *plugin)
char fname[PATH_MAX];
const char *ext, *reason = NULL;
- if (e->d_type != DT_REG)
- reason = "not a regular file";
- else if (e->d_name[0] == '.')
+ if (e->d_name[0] == '.')
reason = "hidden file";
else if (count_dots(e->d_name) > 1)
reason = "backup file";Both work perfectly fine. |
|
Okay, |
1b8c366 to
f233955
Compare
|
Alright, with these considerations, its probably better to just remove that check completely and call it good. |
|
That said: audit-userspace/audisp/audispd-pconfig.c Line 162 in 263414b
this comment is inaccurate, otherwise it should be O_NOFOLLOW/lstatat
|
|
That test was added to make sure we have a real file. We recently had an issue filed because a fifo was in a directory and opening it deadlocked the program. So, if we need to allow symlinks, we need to check that it is a either a regular file or a symlink that resolves to a regular file to pass that first check. |
|
Afaik |
|
Ah, you need to set |
|
I suppose i can do the realpath+stat again (what i had before the force push), but that still allows a potential attacker to "race" and replace a regular file with a blocking char device (at the exact moment of execution after the type check and before the open) to infinitely stall the audit daemon.... Not ideal either. |
|
Ah, i know how to fix it! Hold on, will put an actual clean fix that isn't TOCTOU nor runs into the blocking issues. |
6015f4e to
5f1680d
Compare
|
There, no unnecessary stat, no TOCTOU issues (that i can see), symlink support, and the commits are in an order where at no point there'd be unsafe code, even between changes. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
4b7f481 to
6dde568
Compare
|
Tough challenge...
|
6dde568 to
b854970
Compare
|
i also switched to |
b854970 to
3ef338e
Compare
3ef338e to
516ce23
Compare
`open` might block on FIFO files or some character/block devices such as /dev/tty. Checking `stat` based on path introduces a TOCTOU issue.
`auditd` previously did not support loading plugin configurations from symlinked config files. This is problematic on systems such as NixOS, which constructs basically the entirety of /etc using symlinks. I considered why symlinks were not supported, and concluded the reason was simplicity. While having a symlink point to a writable location would be insecure, a user putting an insecure symlink to trigger this behavior could also immediately do worse things. There also were edge cases if the config file is replaced between the file type check and the actual read. This is because `load_plugin_conf` uses path based logic to check whether a file is a regular file or not, and then asses the file path to `load_pconfig`. This means audispd would already load symlinked configs if a regular file was replaced by a symlink at precisely the right time in execution. `load_pconfig` opens the supplied config file path using `open`. Crucially, it does not set `O_NOFOLLOW`, meaning `load_pconfig` already supports loading plugin configs from symlinks. The check in `load_pconfig` also already uses the file-descriptor based `fstat` call, which mitigates the replacement problems: file descriptors are stable. This means, to support symlinks, it is sufficient to remove the check for regular files from `load_plugin_conf`. This does change internal API: It now is the responsibility of `load_pconfig` to make sure a plugin config file is a regular file. This API change is purely internal, neither `load_pconfig` nor `load_plugin_conf` are part of the public headers. This change has been tested against auditd 4.0.3 and 4.0.5 in a NixOS VM. The plugin config files af_unix.conf, au-remote.conf, filter.conf, syslog.conf all successfully loaded through symlink.
516ce23 to
b8a052a
Compare
b8a052a to
5e75091
Compare
|
Okay, now i am happy. no race-y stat/open, fixed the race with the dirfd, and only actually open the file for reading once all the stat succeeds. This should be good now. |
|
Oh, does this need an entry in the changelog? Sorry, there is no contribution guidelines, so i missed that. |
|
@stevegrubb whats the status here, what do i need to do to move this forward? Do you need me to do specific testing, more explanations, some code changes?
This is now the case, and it won't lock up anymore even without that first preliminary check present anymore. So i am not sure what else i need to do here. Side note: I won't claim there is anything wrong with checking file type, but why exactly are we protecting root from doing something stupid here? It wasn't that much effort and i got an excuse to read more man pages, but it does seem a bit silly to try and accomodate root "accidentially" symlinking /dev/tty into the plugin config directory... |
|
Basically, it needed my time to come back to this. I had a mega patch trying to land. This seems more complicated than it should be, but maybe that's the problem domain. Thanks for the patch! |
|
Thanks for getting this merged, and i do appreciate your work! Indeed this feels on the complicated side, i tried to keep it as simple as possible while properly fixing all edge cases (including TOCTOU and questionable symlinks). Chances are, this could be simpler given more assumptions about what is allowed in the config directory, but alas i was trying to write a thorough patch without imposing any additional assumptions. Now its time to clean this up on our (nixos) side, i am excited to see more security features gaining support. Thanks for your work, not just here but also with |
Upstream PR: linux-audit/audit-userspace#467 (cherry picked from commit 39e8898)
auditd: support loading plugin configs from symlinks
auditdpreviously did not support loading plugin configurations fromsymlinked config files. This is problematic on systems such as NixOS,
which constructs basically the entirety of /etc using symlinks.
I considered why symlinks were not supported, and concluded the reason was
simplicity. While having a symlink point to a writable location would be
insecure, a user putting an insecure symlink to trigger this behavior could
also immediately do worse things.
There also were edge cases if the config file is replaced between the file
type check and the actual read. This is because
load_plugin_confusespath based logic to check whether a file is a regular file or not, and then
asses the file path to
load_pconfig. This means audispd would alreadyload symlinked configs if a regular file was replaced by a symlink at
precisely the right time in execution.
load_pconfigopens the supplied config file path usingopen. Crucially,it does not set
O_NOFOLLOW, meaningload_pconfigalready supportsloading plugin configs from symlinks. The check in
load_pconfigalsoalready uses the file-descriptor based
fstatcall, which mitigates thereplacement problems: file descriptors are stable.
This means, to support symlinks, it is sufficient to remove the check for
regular files from
load_plugin_conf. This does change internal API: Itnow is the responsibility of
load_pconfigto make sure a plugin configfile is a regular file. This API change is purely internal, neither
load_pconfignorload_plugin_confare part of the public headers.This change has been tested against auditd 4.0.3 and 4.0.5 in a NixOS VM.
The plugin config files af_unix.conf, au-remote.conf, filter.conf, syslog.conf
all successfully loaded through symlink.