Skip to content

Commit 2045edd

Browse files
committed
pidfile: open with O_NOFOLLOW to prevent symlink TOCTOU on POSIX
The pidfile path is operator-controlled and typically lives in a root-owned directory like /var/run, so this is not exploitable in the standard threat model. However, if an operator configures a pidfile path in a directory writable by an unprivileged user, that user could pre-place a symlink to cause Falco (running as root) to overwrite an arbitrary file with the PID on startup. Replace the std::ofstream open with a raw POSIX open() using O_NOFOLLOW | O_CLOEXEC on non-Windows platforms. The open will now fail with ELOOP if the path is a symlink, closing the defence-in-depth gap with no behaviour change for legitimate users. Windows builds retain the original std::ofstream path since O_NOFOLLOW / O_CLOEXEC are not available on the Windows toolchain and the Linux-as-root threat model that motivates the hardening does not apply to Falco's Windows build.
1 parent cd1a862 commit 2045edd

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

userspace/falco/app/actions/pidfile.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ See the License for the specific language governing permissions and
1515
limitations under the License.
1616
*/
1717

18+
#ifndef _WIN32
1819
#include <sys/types.h>
1920
#include <sys/stat.h>
2021
#include <fcntl.h>
22+
#include <unistd.h>
23+
#include <cerrno>
24+
#include <cstdio>
25+
#include "compat.h"
26+
#endif
2127

2228
#include "actions.h"
2329

@@ -34,6 +40,35 @@ falco::app::run_result falco::app::actions::pidfile(const falco::app::state& sta
3440
return run_result::ok();
3541
}
3642

43+
#ifndef _WIN32
44+
// O_NOFOLLOW makes open() fail with ELOOP if the path is a symlink, so an
45+
// unprivileged user who can write to the pidfile's directory cannot
46+
// pre-place a symlink and trick a root-running Falco into clobbering an
47+
// arbitrary file with the PID.
48+
int fd = ::open(state.options.pidfilename.c_str(),
49+
O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
50+
0644);
51+
if(fd == -1) {
52+
char errbuf[256];
53+
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
54+
falco_logger::log(falco_logger::level::ERR,
55+
"Could not write pid to pidfile " + state.options.pidfilename +
56+
" (error: " + errstr + "). Exiting.\n");
57+
exit(-1);
58+
}
59+
60+
if(dprintf(fd, "%lld\n", (long long)getpid()) < 0) {
61+
char errbuf[256];
62+
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
63+
falco_logger::log(falco_logger::level::ERR,
64+
"Could not write pid to pidfile " + state.options.pidfilename +
65+
" (error: " + errstr + "). Exiting.\n");
66+
::close(fd);
67+
exit(-1);
68+
}
69+
70+
::close(fd);
71+
#else
3772
int64_t self_pid = getpid();
3873

3974
std::ofstream stream;
@@ -47,6 +82,7 @@ falco::app::run_result falco::app::actions::pidfile(const falco::app::state& sta
4782
}
4883
stream << self_pid;
4984
stream.close();
85+
#endif
5086

5187
return run_result::ok();
5288
}

0 commit comments

Comments
 (0)