teknoraver / rpms / rpm

Forked from rpms/rpm 6 months ago
Clone

Blame 0001-Report-unsafe-symlinks-during-installation-as-a-spec.patch

Michal Domonkos 9caa0d
From fb192da52f3f390793dbaffe66c6148f497a8023 Mon Sep 17 00:00:00 2001
Michal Domonkos 9caa0d
From: Panu Matilainen <pmatilai@redhat.com>
Michal Domonkos 9caa0d
Date: Mon, 19 Aug 2024 11:03:10 +0300
Michal Domonkos 9caa0d
Subject: [PATCH 1/2] Report unsafe symlinks during installation as a specific
Michal Domonkos 9caa0d
 case
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
RPM refuses to follow non root owned symlinks pointing to files owned by
Michal Domonkos 9caa0d
another user for security reasons. This case was lumped in with
Michal Domonkos 9caa0d
O_DIRECTORY behavior, leading to confusing error message as the symlink
Michal Domonkos 9caa0d
often indeed points at a directory. Emit a more meaningful error message
Michal Domonkos 9caa0d
when encountering unsafe symlinks.
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
We already detect the error condition in the main if block here, might
Michal Domonkos 9caa0d
as well set the error code right there and then so we don't need to
Michal Domonkos 9caa0d
redetect later. We previously only tested for the unsafe link condition
Michal Domonkos 9caa0d
when our O_DIRECTORY equivalent was set, but that seems wrong. Probably
Michal Domonkos 9caa0d
doesn't matter with the existing callers, but we really must not
Michal Domonkos 9caa0d
follow those unsafe symlinks no matter what.
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
Co-authored-by: Florian Festi <ffesti@redhat.com>
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
Backported from commits:
Michal Domonkos 9caa0d
14516542c113560dc0070df2f9102568a7a71b58
Michal Domonkos 9caa0d
535eacc96ae6fe5289a2917bb0af43e491b0f4f4
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
Tests are excluded from this backport since they would need significant
Michal Domonkos 9caa0d
rework, the use case will be covered by Beaker.
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
Fixes: RHEL-33393
Michal Domonkos 9caa0d
---
Michal Domonkos 9caa0d
 lib/fsm.c   | 70 +++++++++++++++++++++++++++--------------------------
Michal Domonkos 9caa0d
 lib/rpmfi.c |  1 +
Michal Domonkos 9caa0d
 2 files changed, 37 insertions(+), 34 deletions(-)
Michal Domonkos 9caa0d
Michal Domonkos 9caa0d
diff --git a/lib/fsm.c b/lib/fsm.c
Michal Domonkos 9caa0d
index 9dd50b784..720d4a2ec 100644
Michal Domonkos 9caa0d
--- a/lib/fsm.c
Michal Domonkos 9caa0d
+++ b/lib/fsm.c
Michal Domonkos 9caa0d
@@ -65,7 +65,7 @@ struct filedata_s {
Michal Domonkos 9caa0d
  * things around needlessly 
Michal Domonkos 9caa0d
  */ 
Michal Domonkos 9caa0d
 static const char * fileActionString(rpmFileAction a);
Michal Domonkos 9caa0d
-static int fsmOpenat(int dirfd, const char *path, int flags, int dir);
Michal Domonkos 9caa0d
+static int fsmOpenat(int *fdp, int dirfd, const char *path, int flags, int dir);
Michal Domonkos 9caa0d
 static int fsmClose(int *wfdp);
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
 /** \ingroup payload
Michal Domonkos 9caa0d
@@ -98,9 +98,9 @@ static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path)
Michal Domonkos 9caa0d
 #ifdef WITH_CAP
Michal Domonkos 9caa0d
 static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps)
Michal Domonkos 9caa0d
 {
Michal Domonkos 9caa0d
-    int rc = -1;
Michal Domonkos 9caa0d
-    int fd = fsmOpenat(dirfd, path, O_RDONLY|O_NOFOLLOW, 0);
Michal Domonkos 9caa0d
-    if (fd >= 0) {
Michal Domonkos 9caa0d
+    int fd = -1;
Michal Domonkos 9caa0d
+    int rc = fsmOpenat(&fd, dirfd, path, O_RDONLY|O_NOFOLLOW, 0);
Michal Domonkos 9caa0d
+    if (!rc) {
Michal Domonkos 9caa0d
 	rc = cap_set_fd(fd, fcaps);
Michal Domonkos 9caa0d
 	fsmClose(&fd;;
Michal Domonkos 9caa0d
     }
Michal Domonkos 9caa0d
@@ -299,12 +299,12 @@ static int fsmMkdir(int dirfd, const char *path, mode_t mode)
Michal Domonkos 9caa0d
     return rc;
Michal Domonkos 9caa0d
 }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
-static int fsmOpenat(int dirfd, const char *path, int flags, int dir)
Michal Domonkos 9caa0d
+static int fsmOpenat(int *wfdp, int dirfd, const char *path, int flags, int dir)
Michal Domonkos 9caa0d
 {
Michal Domonkos 9caa0d
     struct stat lsb, sb;
Michal Domonkos 9caa0d
     int sflags = flags | O_NOFOLLOW;
Michal Domonkos 9caa0d
     int fd = openat(dirfd, path, sflags);
Michal Domonkos 9caa0d
-    int ffd = fd;
Michal Domonkos 9caa0d
+    int rc = 0;
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     /*
Michal Domonkos 9caa0d
      * Only ever follow symlinks by root or target owner. Since we can't
Michal Domonkos 9caa0d
@@ -313,7 +313,7 @@ static int fsmOpenat(int dirfd, const char *path, int flags, int dir)
Michal Domonkos 9caa0d
      * it could've only been the link owner or root.
Michal Domonkos 9caa0d
      */
Michal Domonkos 9caa0d
     if (fd < 0 && errno == ELOOP && flags != sflags) {
Michal Domonkos 9caa0d
-	ffd = openat(dirfd, path, flags);
Michal Domonkos 9caa0d
+	int ffd = openat(dirfd, path, flags);
Michal Domonkos 9caa0d
 	if (ffd >= 0) {
Michal Domonkos 9caa0d
 	    if (fstatat(dirfd, path, &lsb, AT_SYMLINK_NOFOLLOW) == 0) {
Michal Domonkos 9caa0d
 		if (fstat(ffd, &sb) == 0) {
Michal Domonkos 9caa0d
@@ -322,17 +322,26 @@ static int fsmOpenat(int dirfd, const char *path, int flags, int dir)
Michal Domonkos 9caa0d
 		    }
Michal Domonkos 9caa0d
 		}
Michal Domonkos 9caa0d
 	    }
Michal Domonkos 9caa0d
-	    if (ffd != fd)
Michal Domonkos 9caa0d
+	    /* Symlink with non-matching owners */
Michal Domonkos 9caa0d
+	    if (ffd != fd) {
Michal Domonkos 9caa0d
 		close(ffd);
Michal Domonkos 9caa0d
+		rc = RPMERR_INVALID_SYMLINK;
Michal Domonkos 9caa0d
+	    }
Michal Domonkos 9caa0d
 	}
Michal Domonkos 9caa0d
     }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     /* O_DIRECTORY equivalent */
Michal Domonkos 9caa0d
-    if (dir && ((fd != ffd) || (fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)))) {
Michal Domonkos 9caa0d
-	errno = ENOTDIR;
Michal Domonkos 9caa0d
+    if (!rc && dir && fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode))
Michal Domonkos 9caa0d
+	rc = RPMERR_ENOTDIR;
Michal Domonkos 9caa0d
+
Michal Domonkos 9caa0d
+    if (!rc && fd < 0)
Michal Domonkos 9caa0d
+	rc = RPMERR_OPEN_FAILED;
Michal Domonkos 9caa0d
+
Michal Domonkos 9caa0d
+    if (rc)
Michal Domonkos 9caa0d
 	fsmClose(&fd;;
Michal Domonkos 9caa0d
-    }
Michal Domonkos 9caa0d
-    return fd;
Michal Domonkos 9caa0d
+
Michal Domonkos 9caa0d
+    *wfdp = fd;
Michal Domonkos 9caa0d
+    return rc;
Michal Domonkos 9caa0d
 }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
 static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
Michal Domonkos 9caa0d
@@ -351,9 +360,7 @@ static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
Michal Domonkos 9caa0d
 	rc = fsmMkdir(dirfd, dn, mode);
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     if (!rc) {
Michal Domonkos 9caa0d
-	*fdp = fsmOpenat(dirfd, dn, O_RDONLY|O_NOFOLLOW, 1);
Michal Domonkos 9caa0d
-	if (*fdp == -1)
Michal Domonkos 9caa0d
-	    rc = RPMERR_ENOTDIR;
Michal Domonkos 9caa0d
+	rc = fsmOpenat(fdp, dirfd, dn, O_RDONLY|O_NOFOLLOW, 1);
Michal Domonkos 9caa0d
     }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     if (!rc) {
Michal Domonkos 9caa0d
@@ -378,47 +385,44 @@ static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
Michal Domonkos 9caa0d
     char *sp = NULL, *bn;
Michal Domonkos 9caa0d
     char *apath = NULL;
Michal Domonkos 9caa0d
     int oflags = O_RDONLY;
Michal Domonkos 9caa0d
-    int rc = 0;
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     if (*dirfdp >= 0)
Michal Domonkos 9caa0d
-	return rc;
Michal Domonkos 9caa0d
+	return 0;
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
-    int dirfd = fsmOpenat(-1, "/", oflags, 1);
Michal Domonkos 9caa0d
+    int dirfd = -1;
Michal Domonkos 9caa0d
+    int rc = fsmOpenat(&dirfd, -1, "/", oflags, 1);
Michal Domonkos 9caa0d
     int fd = dirfd; /* special case of "/" */
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     char *path = xstrdup(p);
Michal Domonkos 9caa0d
     char *dp = path;
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     while ((bn = strtok_r(dp, "/", &sp)) != NULL) {
Michal Domonkos 9caa0d
-	fd = fsmOpenat(dirfd, bn, oflags, 1);
Michal Domonkos 9caa0d
+	rc = fsmOpenat(&fd, dirfd, bn, oflags, 1);
Michal Domonkos 9caa0d
 	/* assemble absolute path for plugins benefit, sigh */
Michal Domonkos 9caa0d
 	apath = rstrscat(&apath, "/", bn, NULL);
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
-	if (fd < 0 && errno == ENOENT && create) {
Michal Domonkos 9caa0d
+	if (rc && errno == ENOENT && create) {
Michal Domonkos 9caa0d
 	    mode_t mode = S_IFDIR | (_dirPerms & 07777);
Michal Domonkos 9caa0d
 	    rc = fsmDoMkDir(plugins, dirfd, bn, apath, owned, mode, &fd;;
Michal Domonkos 9caa0d
 	}
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
 	fsmClose(&dirfd);
Michal Domonkos 9caa0d
-	if (fd >= 0) {
Michal Domonkos 9caa0d
-	    dirfd = fd;
Michal Domonkos 9caa0d
-	} else {
Michal Domonkos 9caa0d
-	    if (!quiet) {
Michal Domonkos 9caa0d
-		rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"),
Michal Domonkos 9caa0d
-			bn, p, strerror(errno));
Michal Domonkos 9caa0d
-	    }
Michal Domonkos 9caa0d
-	    rc = RPMERR_OPEN_FAILED;
Michal Domonkos 9caa0d
+	if (rc)
Michal Domonkos 9caa0d
 	    break;
Michal Domonkos 9caa0d
-	}
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
+	dirfd = fd;
Michal Domonkos 9caa0d
 	dp = NULL;
Michal Domonkos 9caa0d
     }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
     if (rc) {
Michal Domonkos 9caa0d
+	if (!quiet) {
Michal Domonkos 9caa0d
+	    char *msg = rpmfileStrerror(rc);
Michal Domonkos 9caa0d
+	    rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"),
Michal Domonkos 9caa0d
+		    bn, p, msg);
Michal Domonkos 9caa0d
+	    free(msg);
Michal Domonkos 9caa0d
+	}
Michal Domonkos 9caa0d
 	fsmClose(&fd;;
Michal Domonkos 9caa0d
 	fsmClose(&dirfd);
Michal Domonkos 9caa0d
-    } else {
Michal Domonkos 9caa0d
-	rc = 0;
Michal Domonkos 9caa0d
     }
Michal Domonkos 9caa0d
     *dirfdp = dirfd;
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
@@ -1025,10 +1029,8 @@ setmeta:
Michal Domonkos 9caa0d
 		/* Only follow safe symlinks, and never on temporary files */
Michal Domonkos 9caa0d
 		if (fp->suffix)
Michal Domonkos 9caa0d
 		    flags |= AT_SYMLINK_NOFOLLOW;
Michal Domonkos 9caa0d
-		fd = fsmOpenat(di.dirfd, fp->fpath, flags,
Michal Domonkos 9caa0d
+		rc = fsmOpenat(&fd, di.dirfd, fp->fpath, flags,
Michal Domonkos 9caa0d
 				S_ISDIR(fp->sb.st_mode));
Michal Domonkos 9caa0d
-		if (fd < 0)
Michal Domonkos 9caa0d
-		    rc = RPMERR_OPEN_FAILED;
Michal Domonkos 9caa0d
 	    }
Michal Domonkos 9caa0d
 
Michal Domonkos 9caa0d
 	    if (!rc && fp->setmeta) {
Michal Domonkos 9caa0d
diff --git a/lib/rpmfi.c b/lib/rpmfi.c
Michal Domonkos 9caa0d
index b5223a43f..e8e3ea294 100644
Michal Domonkos 9caa0d
--- a/lib/rpmfi.c
Michal Domonkos 9caa0d
+++ b/lib/rpmfi.c
Michal Domonkos 9caa0d
@@ -2436,6 +2436,7 @@ char * rpmfileStrerror(int rc)
Michal Domonkos 9caa0d
     case RPMERR_DIGEST_MISMATCH: s = _("Digest mismatch");	break;
Michal Domonkos 9caa0d
     case RPMERR_INTERNAL:	s = _("Internal error");	break;
Michal Domonkos 9caa0d
     case RPMERR_UNMAPPED_FILE:	s = _("Archive file not in header"); break;
Michal Domonkos 9caa0d
+    case RPMERR_INVALID_SYMLINK: s = _("Unsafe symlink");	break;
Michal Domonkos 9caa0d
     case RPMERR_ENOENT:	s = strerror(ENOENT); break;
Michal Domonkos 9caa0d
     case RPMERR_ENOTEMPTY:	s = strerror(ENOTEMPTY); break;
Michal Domonkos 9caa0d
     case RPMERR_EXIST_AS_DIR:
Michal Domonkos 9caa0d
-- 
Michal Domonkos 9caa0d
2.47.1
Michal Domonkos 9caa0d