[PATCH] crontab: use O_NOFOLLOW when opening files in the spool dir

Ali Ahmet Memis via busybox <[email protected]>
Newsgroups gmane.linux.busybox
Message-ID <[email protected]>
crontab is setuid root (BB_SUID_REQUIRE) and opens files in the crontab
spool directory while running with euid 0.

If the spool directory is writable by an unprivileged user, they can
create a symlink with one of the filenames used by crontab. Since the
files are currently opened without O_NOFOLLOW, the symlink is followed
and the target is modified as root.

This affects the following files:

- <user>.new is opened with O_CREAT|O_TRUNC, so a symlink can cause an
  arbitrary file to be truncated and overwritten.
- the temporary file used by -e is opened the same way.
- cron.update is opened with O_CREAT|O_APPEND, allowing data to be
  appended to an arbitrary file.

For example, a symlink can point to another user's crontab or another
root-owned file, resulting in a write-as-root primitive.

Use O_NOFOLLOW when opening these files so that a symlink as the final
path component is rejected.

There is no behavior change for a normally configured spool directory,
which is owned by root and not writable by users. Unprivileged users
can still use crontab through the setuid binary, since the file
operations are performed by the process itself.

Signed-off-by: Ali Ahmet Memis <[email protected]>
---
 miscutils/crontab.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/miscutils/crontab.c b/miscutils/crontab.c
index 1111f4d54..ae36fcca3 100644
--- a/miscutils/crontab.c
+++ b/miscutils/crontab.c
@@ -157,7 +157,8 @@ int crontab_main(int argc UNUSED_PARAM, char **argv)
 		tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid());
 		/* No O_EXCL: we don't want to be stuck if earlier crontabs
 		 * were killed, leaving stale temp file behind */
-		src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+		/* O_NOFOLLOW: we are root, don't follow a planted symlink */
+		src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC|O_NOFOLLOW, 0600);
 		fchown(src_fd, pas->pw_uid, pas->pw_gid);
 		fd = open(pas->pw_name, O_RDONLY);
 		if (fd >= 0) {
@@ -170,12 +171,12 @@ int crontab_main(int argc UNUSED_PARAM, char **argv)
 		/* The src_fd needs to be reopened to handle editors that do
 		 * save the buffer as new file and rename it to tmp_fname (so
 		 * for example vim). */
-		src_fd = xopen3(tmp_fname, O_RDONLY, 0600);
+		src_fd = xopen3(tmp_fname, O_RDONLY|O_NOFOLLOW, 0600);
 		/* fall through */
 
 	case 0: /* Replace (no -l, -e, or -r were given) */
 		new_fname = xasprintf("%s.new", pas->pw_name);
-		fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
+		fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND|O_NOFOLLOW, 0600);
 		if (fd >= 0) {
 			bb_copyfd_eof(src_fd, fd);
 			close(fd);
@@ -193,7 +194,7 @@ int crontab_main(int argc UNUSED_PARAM, char **argv)
 	/* Bump notification file.  Handle window where crond picks file up
 	 * before we can write our entry out.
 	 */
-	while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
+	while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND|O_NOFOLLOW, 0600)) >= 0) {
 		struct stat st;
 
 		fdprintf(fd, "%s\n", pas->pw_name);
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.