Skip to content

Commit b51166a

Browse files
committed
Fix (relocatable) packages that own / in fsm
During payload processing, if we encounter a file entry corresponding to the prefix directory itself, instead of referring to it relative to its parent directory's file descriptor, we pass the absolute / path to the *at() calls which then makes them fall back to path-based operation. This isn't a huge problem as long as we're installing into the real root prefix, however if the package is built as relocatable and the prefix is overridden, we still operate on the real "/" instead of the new prefix. This is wrong and even causes an installation failure if the relocatable package is installed as a regular user who doesn't have write access for the real root. Fix this by simply passing "." instead of "/" to these *at() calls, to always ensure fd-based operation. However, this alone isn't sufficient since we can't delete the relocated prefix directory itself this way when removing the package. This is due to the fact that unlinkat(2) with AT_REMOVEDIR actually does a rmdir(2), and that fails with EINVAL if called with the "." path. Therefore, when removing a package and encountering the prefix directory itself, stop the traversal in ensureDir() one level higher than usual so that we refer to the prefix directory relative to its parent directory via normal fd-based operation. Lastly, don't even attempt to remove the prefix if it's *not* relocated. This is safer and also gets rid of a spurious warning that's printed when removing a non-relocatable package owning / as the root user. Such packages (that include the sole / in their %files section) perhaps aren't exactly common and those that exist aren't normally installed or uninstalled on a production system (such as the "filesystem" package on Fedora), however it's apparently used as a shorthand for including all files in a relocatable package, as evidenced by TBD. Either way, this is a regression introduced by the fsm rework addressing the symlink CVEs (see rpm-software-management#1919 for details). Fixes: TBD
1 parent c38abe2 commit b51166a

File tree

3 files changed

+101
-8
lines changed

3 files changed

+101
-8
lines changed

lib/fsm.c

+38-8
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ static int fsmClose(int *wfdp);
7878
static char * fsmFsPath(rpmfi fi, const char * suffix)
7979
{
8080
const char *bn = rpmfiBN(fi);
81-
return rstrscat(NULL, *bn ? bn : "/", suffix ? suffix : "", NULL);
81+
return rstrscat(NULL, *bn ? bn : ".", suffix ? suffix : "", NULL);
8282
}
8383

8484
static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path)
@@ -373,7 +373,7 @@ static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
373373
}
374374

375375
static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
376-
int quiet, int *dirfdp)
376+
int quiet, int *dirfdp, char **dirfnp)
377377
{
378378
char *sp = NULL, *bn;
379379
char *apath = NULL;
@@ -390,6 +390,12 @@ static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
390390
char *dp = path;
391391

392392
while ((bn = strtok_r(dp, "/", &sp)) != NULL) {
393+
/* stop at parent dir if requested (and return its name) */
394+
if (dirfnp && !(*sp)) {
395+
*dirfnp = xstrdup(bn);
396+
break;
397+
}
398+
393399
fd = fsmOpenat(dirfd, bn, oflags, 1);
394400
/* assemble absolute path for plugins benefit, sigh */
395401
apath = rstrscat(&apath, "/", bn, NULL);
@@ -417,6 +423,8 @@ static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
417423
if (rc) {
418424
fsmClose(&fd);
419425
fsmClose(&dirfd);
426+
if (dirfnp)
427+
free(*dirfnp);
420428
} else {
421429
rc = 0;
422430
}
@@ -945,7 +953,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
945953
int mayopen = 0;
946954
int fd = -1;
947955
rc = ensureDir(plugins, rpmfiDN(fi), 0,
948-
(fp->action == FA_CREATE), 0, &di.dirfd);
956+
(fp->action == FA_CREATE), 0, &di.dirfd, NULL);
949957

950958
/* Directories replacing something need early backup */
951959
if (!rc && !fp->suffix && fp != firstlink) {
@@ -1061,7 +1069,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
10611069

10621070
if (!fp->skip) {
10631071
if (!rc)
1064-
rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd);
1072+
rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd, NULL);
10651073

10661074
/* Backup file if needed. Directories are handled earlier */
10671075
if (!rc && fp->suffix)
@@ -1089,7 +1097,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
10891097
struct filedata_s *fp = &fdata[fx];
10901098

10911099
/* If the directory doesn't exist there's nothing to clean up */
1092-
if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
1100+
if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd, NULL))
10931101
continue;
10941102

10951103
if (fp->stage > FILE_NONE && !fp->skip) {
@@ -1124,6 +1132,8 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
11241132
int fx = -1;
11251133
struct filedata_s *fdata = (struct filedata_s *)xcalloc(fc, sizeof(*fdata));
11261134
int rc = 0;
1135+
int rootdir = 0;
1136+
char *dirfn = NULL;
11271137

11281138
while (!rc && (fx = rpmfiNext(fi)) >= 0) {
11291139
struct filedata_s *fp = &fdata[fx];
@@ -1133,8 +1143,24 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
11331143
continue;
11341144

11351145
fp->fpath = fsmFsPath(fi, NULL);
1146+
rootdir = rstreq(fp->fpath, ".");
1147+
1148+
/*
1149+
* Handle the case where the path is the root directory itself. If
1150+
* we're in the real root, don't even attempt to unlink it. Otherwise,
1151+
* we're in a relocated root which we do want to unlink, so manually
1152+
* signal a directory change for "di" to be re-populated with the
1153+
* parent directory (trying to unlink the "." path would result in an
1154+
* EINVAL error).
1155+
*/
1156+
if (rstreq(rpmfiDN(fi), "/"))
1157+
continue;
1158+
else if (rootdir)
1159+
onChdir(fi, &di.dirfd);
1160+
11361161
/* If the directory doesn't exist there's nothing to clean up */
1137-
if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
1162+
if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd,
1163+
(rootdir ? &dirfn : NULL)))
11381164
continue;
11391165

11401166
rc = fsmStat(di.dirfd, fp->fpath, 1, &fp->sb);
@@ -1145,13 +1171,14 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
11451171
rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
11461172
fp->sb.st_mode, fp->action);
11471173

1148-
rc = fsmBackup(di.dirfd, fi, fp->action);
1174+
if (!rootdir)
1175+
rc = fsmBackup(di.dirfd, fi, fp->action);
11491176

11501177
/* Remove erased files. */
11511178
if (fp->action == FA_ERASE) {
11521179
int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
11531180

1154-
rc = fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode);
1181+
rc = fsmRemove(di.dirfd, (rootdir ? dirfn : fp->fpath), fp->sb.st_mode);
11551182

11561183
/*
11571184
* Missing %ghost or %missingok entries are not errors.
@@ -1197,6 +1224,9 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
11971224
rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
11981225
rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
11991226
}
1227+
1228+
free(dirfn);
1229+
dirfn = NULL;
12001230
}
12011231

12021232
for (int i = 0; i < fc; i++)

tests/data/SPECS/reloc2.spec

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Name: reloc2
2+
Version: 1.0
3+
Release: 1
4+
Summary: Testing relocation behavior with prefix ownership
5+
License: GPL
6+
Prefix: /
7+
BuildArch: noarch
8+
9+
%description
10+
%{summary}.
11+
12+
%install
13+
mkdir -p $RPM_BUILD_ROOT/foo
14+
touch $RPM_BUILD_ROOT/foo/bar
15+
16+
%files
17+
%{prefix}

tests/rpmi.at

+46
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,52 @@ runroot rpm -U --relocate /opt/bin=/bin \
11351135
[])
11361136
RPMTEST_CLEANUP
11371137

1138+
AT_SETUP([rpm -i relocatable package owning prefix])
1139+
AT_KEYWORDS([install relocate])
1140+
RPMDB_INIT
1141+
1142+
runroot rpmbuild --quiet -bb /data/SPECS/reloc2.spec
1143+
1144+
RPMTEST_CHECK([
1145+
runroot rpm -U /build/RPMS/noarch/reloc2-1.0-1.noarch.rpm
1146+
],
1147+
[0],
1148+
[],
1149+
[])
1150+
1151+
RPMTEST_CHECK([
1152+
runroot rpm -e reloc2
1153+
],
1154+
[0],
1155+
[],
1156+
[])
1157+
RPMTEST_CLEANUP
1158+
1159+
AT_SETUP([rpm -i relocatable package owning prefix (user)])
1160+
AT_KEYWORDS([install relocate])
1161+
RPMTEST_USER
1162+
RPMDB_INIT
1163+
1164+
runroot rpmbuild --quiet -bb /data/SPECS/reloc2.spec
1165+
1166+
RPMTEST_CHECK([
1167+
runroot_user \
1168+
rpm -U --prefix \$PWD/root --dbpath \$PWD/rpmdb \
1169+
/build/RPMS/noarch/reloc2-1.0-1.noarch.rpm
1170+
],
1171+
[0],
1172+
[],
1173+
[])
1174+
1175+
RPMTEST_CHECK([
1176+
runroot_user rpm -e --dbpath \$PWD/rpmdb reloc2
1177+
runroot_user test ! -d \$PWD/root
1178+
],
1179+
[0],
1180+
[],
1181+
[])
1182+
RPMTEST_CLEANUP
1183+
11381184
AT_SETUP([rpm -i with/without --excludedocs])
11391185
AT_KEYWORDS([install excludedocs])
11401186
RPMTEST_CHECK([

0 commit comments

Comments
 (0)