Code: int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
              rpmpsm psm, char ** failedFile)
{
    FD_t payload = rpmtePayload(te);
    rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
    rpmfs fs = rpmteGetFileStates(te);
    rpmPlugins plugins = rpmtsPlugins(ts);
    struct stat sb;
    int saveerrno = errno;
    int rc = 0;
    int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
    int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
    int firsthardlink = -1;
    int skip;
    rpmFileAction action;
    char *tid = NULL;
    const char *suffix;
    char *fpath = NULL;

    if (fi == NULL) {
	rc = RPMERR_BAD_MAGIC;
	goto exit;
    }

    /* transaction id used for temporary path suffix while installing */
    rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));

    /* Detect and create directories not explicitly in package. */
    rc = fsmMkdirs(files, fs, plugins);

    while (!rc) {
	/* Read next payload header. */
	rc = rpmfiNext(fi);

	if (rc < 0) {
	    if (rc == RPMERR_ITER_END)
		rc = 0;
	    break;
	}

	action = rpmfsGetAction(fs, rpmfiFX(fi));
	skip = XFA_SKIPPING(action);
	suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
	if (action != FA_TOUCH) {
	    fpath = fsmFsPath(fi, suffix);
	} else {
	    fpath = fsmFsPath(fi, "");
	}

	/* Remap file perms, owner, and group. */
	rc = rpmfiStat(fi, 1, &sb);

	fsmDebug(fpath, action, &sb);

        /* Exit on error. */
        if (rc)
            break;

	/* Run fsm file pre hook for all plugins */
	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
				      sb.st_mode, action);
	if (rc) {
	    skip = 1;
	} else {
	    setFileState(fs, rpmfiFX(fi));
	}

        if (!skip) {
	    int setmeta = 1;

	    /* Directories replacing something need early backup */
	    if (!suffix) {
		rc = fsmBackup(fi, action);
 	    }
 	    /* Assume file does't exist when tmp suffix is in use */
 	    if (!suffix) {
		rc = fsmVerify(fpath, fi);
 	    } else {
 		rc = (action == FA_TOUCH) ? 0 : RPMERR_ENOENT;
 	    }

            if (S_ISREG(sb.st_mode)) {
		if (rc == RPMERR_ENOENT) {
		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
				   &setmeta, &firsthardlink);
		}
            } else if (S_ISDIR(sb.st_mode)) {
                if (rc == RPMERR_ENOENT) {
                    mode_t mode = sb.st_mode;
                    mode &= ~07777;
                    mode |=  00700;
                    rc = fsmMkdir(fpath, mode);
                }
            } else if (S_ISLNK(sb.st_mode)) {
		if (rc == RPMERR_ENOENT) {
		    rc = fsmSymlink(rpmfiFLink(fi), fpath);
		}
            } else if (S_ISFIFO(sb.st_mode)) {
                /* This mimics cpio S_ISSOCK() behavior but probably isn't right */
                if (rc == RPMERR_ENOENT) {
                    rc = fsmMkfifo(fpath, 0000);
                }
            } else if (S_ISCHR(sb.st_mode) ||
                       S_ISBLK(sb.st_mode) ||
                       S_ISSOCK(sb.st_mode))
            {
                if (rc == RPMERR_ENOENT) {
                    rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
                }
            } else {
                /* XXX Special case /dev/log, which shouldn't be packaged anyways */
                if (!IS_DEV_LOG(fpath))
                    rc = RPMERR_UNKNOWN_FILETYPE;
            }
	    /* Set permissions, timestamps etc for non-hardlink entries */
	    if (!rc && setmeta) {
		rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
	    }
        } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
	    /* we skip the hard linked file containing the content */
	    /* write the content to the first used instead */
	    char *fn = rpmfilesFN(files, firsthardlink);
	    rc = expandRegular(fi, fn, psm, 0, nodigest, 0);
	    firsthardlink = -1;
	    free(fn);
	}

        if (rc) {
            if (!skip) {
                /* XXX only erase if temp fn w suffix is in use */
                if (suffix && (action != FA_TOUCH)) {
		    (void) fsmRemove(fpath, sb.st_mode);
                }
                errno = saveerrno;
            }
        } else {
	    /* Notify on success. */
	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));

	    if (!skip) {
		/* Backup file if needed. Directories are handled earlier */
		if (suffix)
		    rc = fsmBackup(fi, action);

		if (!rc)
		    rc = fsmCommit(&fpath, fi, action, suffix);
	    }
	}

	if (rc)
	    *failedFile = xstrdup(fpath);

	/* Run fsm file post hook for all plugins */
	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
				  sb.st_mode, action, rc);
	fpath = _free(fpath);
    }

    rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
    rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));

exit:

    /* No need to bother with close errors on read */
    rpmfiArchiveClose(fi);
    rpmfiFree(fi);
    Fclose(payload);
    free(tid);
    free(fpath);

    return rc;
}

Vulnerability Type: +Priv 
CWE ID: CWE-59
Summary: It was found that rpm did not properly handle RPM installations when a destination path was a symbolic link to a directory, possibly changing ownership and permissions of an arbitrary directory, and RPM files being placed in an arbitrary destination. An attacker, with write access to a directory in which a subdirectory will be installed, could redirect that directory to an arbitrary location and gain root privilege.
Commit Message: Restrict following symlinks to directories by ownership (CVE-2017-7500)

Only follow directory symlinks owned by target directory owner or root.
This prevents privilege escalation from user-writable directories via
directory symlinks to privileged directories on package upgrade, while
still allowing admin to arrange disk usage with symlinks.

The rationale is that if you can create symlinks owned by user X you *are*
user X (or root), and if you also own directory Y you can do whatever with
it already, including change permissions. So when you create a symlink to
that directory, the link ownership acts as a simple stamp of authority that
you indeed want rpm to treat this symlink as it were the directory that
you own. Such a permission can only be given by you or root, which
is just the way we want it. Plus it's almost ridiculously simple as far
as rules go, compared to trying to calculate something from the
source vs destination directory permissions etc.

In the normal case, the user arranging diskspace with symlinks is indeed
root so nothing changes, the only real change here is to links created by
non-privileged users which should be few and far between in practise.
Unfortunately our test-suite runs as a regular user via fakechroot and
thus the testcase for this fails under the new rules. Adjust the testcase
to get the ownership straight and add a second case for the illegal
behavior, basically the same as the old one but with different expectations.