1 /** \ingroup rpmcli
2  * \file lib/verify.c
3  * Verify installed payload files from package metadata.
4  */
5 
6 #include "system.h"
7 
8 #include <errno.h>
9 #include <fcntl.h>
10 #if WITH_CAP
11 #include <sys/capability.h>
12 #endif
13 #if WITH_ACL
14 #include <acl/libacl.h>
15 #endif
16 
17 #include <rpm/rpmcli.h>
18 #include <rpm/header.h>
19 #include <rpm/rpmlog.h>
20 #include <rpm/rpmfi.h>
21 #include <rpm/rpmts.h>
22 #include <rpm/rpmdb.h>
23 #include <rpm/rpmfileutil.h>
24 
25 #include "lib/misc.h"
26 #include "lib/rpmchroot.h"
27 #include "lib/rpmte_internal.h"	/* rpmteProcess() */
28 #include "lib/rpmug.h"
29 
30 #include "debug.h"
31 
32 #define S_ISDEV(m) (S_ISBLK((m)) || S_ISCHR((m)))
33 
34 /* If cap_compare() (Linux extension) not available, do it the hard way */
35 #if WITH_CAP && !defined(HAVE_CAP_COMPARE)
cap_compare(cap_t acap,cap_t bcap)36 static int cap_compare(cap_t acap, cap_t bcap)
37 {
38     int rc = 0;
39     size_t asize = cap_size(acap);
40     size_t bsize = cap_size(bcap);
41 
42     if (asize != bsize) {
43 	rc = 1;
44     } else {
45 	char *abuf = xcalloc(asize, sizeof(*abuf));
46 	char *bbuf = xcalloc(bsize, sizeof(*bbuf));
47 	cap_copy_ext(abuf, acap, asize);
48 	cap_copy_ext(bbuf, bcap, bsize);
49 	rc = memcmp(abuf, bbuf, asize);
50 	free(abuf);
51 	free(bbuf);
52     }
53     return rc;
54 }
55 #endif
56 
rpmfilesVerify(rpmfiles fi,int ix,rpmVerifyAttrs omitMask)57 rpmVerifyAttrs rpmfilesVerify(rpmfiles fi, int ix, rpmVerifyAttrs omitMask)
58 {
59     rpmfileAttrs fileAttrs = rpmfilesFFlags(fi, ix);
60     rpmVerifyAttrs flags = rpmfilesVFlags(fi, ix);
61     char * fn = rpmfilesFN(fi, ix);
62     struct stat sb, fsb;
63     rpmVerifyAttrs vfy = RPMVERIFY_NONE;
64 
65     /*
66      * Check to see if the file was installed - if not pretend all is OK.
67      */
68     switch (rpmfilesFState(fi, ix)) {
69     case RPMFILE_STATE_NETSHARED:
70     case RPMFILE_STATE_NOTINSTALLED:
71 	goto exit;
72 	break;
73     case RPMFILE_STATE_REPLACED:
74 	/* For replaced files we can only verify if it exists at all */
75 	flags = RPMVERIFY_LSTATFAIL;
76 	break;
77     case RPMFILE_STATE_WRONGCOLOR:
78 	/*
79 	 * Files with wrong color are supposed to share some attributes
80 	 * with the actually installed file - verify what we can.
81 	 */
82 	flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE |
83 		   RPMVERIFY_MTIME | RPMVERIFY_RDEV);
84 	break;
85     case RPMFILE_STATE_NORMAL:
86     /* File from a non-installed package, try to verify nevertheless */
87     case RPMFILE_STATE_MISSING:
88 	break;
89     }
90 
91     if (fn == NULL || lstat(fn, &sb) != 0 || rpmfilesStat(fi, ix, 0, &fsb)) {
92 	vfy |= RPMVERIFY_LSTATFAIL;
93 	goto exit;
94     }
95 
96     /* If we expected a directory but got a symlink to one, follow the link */
97     if (S_ISDIR(fsb.st_mode) && S_ISLNK(sb.st_mode)) {
98 	struct stat dsb;
99 	/* ...if it actually points to a directory  */
100 	if (stat(fn, &dsb) == 0 && S_ISDIR(dsb.st_mode)) {
101 	    /* ...and is by a legit user, to match fsmVerify() behavior */
102 	    if (sb.st_uid == 0 || sb.st_uid == dsb.st_uid)
103 		sb = dsb; /* struct assignment */
104 	}
105     }
106 
107     /* Links have no mode, other types have no linkto */
108     if (S_ISLNK(sb.st_mode))
109 	flags &= ~(RPMVERIFY_MODE);
110     else
111 	flags &= ~(RPMVERIFY_LINKTO);
112 
113     /* Not all attributes of non-regular files can be verified */
114     if (!S_ISREG(sb.st_mode))
115 	flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE |
116 		   RPMVERIFY_MTIME | RPMVERIFY_CAPS);
117 
118     /* Content checks of %ghost files are meaningless. */
119     if (fileAttrs & RPMFILE_GHOST)
120 	flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE |
121 		   RPMVERIFY_MTIME | RPMVERIFY_LINKTO);
122 
123     /* Don't verify any features in omitMask. */
124     flags &= ~(omitMask | RPMVERIFY_FAILURES);
125 
126 
127     if (flags & RPMVERIFY_FILEDIGEST) {
128 	const unsigned char *digest;
129 	int algo;
130 	size_t diglen;
131 
132 	if ((digest = rpmfilesFDigest(fi, ix, &algo, &diglen))) {
133 	    unsigned char fdigest[diglen];
134 
135 	    if (rpmDoDigest(algo, fn, 0, fdigest)) {
136 		vfy |= (RPMVERIFY_READFAIL|RPMVERIFY_FILEDIGEST);
137 	    } else {
138 		if (memcmp(fdigest, digest, diglen))
139 		    vfy |= RPMVERIFY_FILEDIGEST;
140 	    }
141 	} else {
142 	    vfy |= RPMVERIFY_FILEDIGEST;
143 	}
144     }
145 
146     if (flags & RPMVERIFY_LINKTO) {
147 	char linkto[1024+1];
148 	int size = 0;
149 
150 	if ((size = readlink(fn, linkto, sizeof(linkto)-1)) == -1)
151 	    vfy |= (RPMVERIFY_READLINKFAIL|RPMVERIFY_LINKTO);
152 	else {
153 	    const char * flink = rpmfilesFLink(fi, ix);
154 	    linkto[size] = '\0';
155 	    if (flink == NULL || !rstreq(linkto, flink))
156 		vfy |= RPMVERIFY_LINKTO;
157 	}
158     }
159 
160     if ((flags & RPMVERIFY_FILESIZE) && (sb.st_size != fsb.st_size))
161 	vfy |= RPMVERIFY_FILESIZE;
162 
163     if (flags & RPMVERIFY_MODE) {
164 	mode_t metamode = fsb.st_mode;
165 	mode_t filemode = sb.st_mode;
166 
167 	/*
168 	 * Comparing the type of %ghost files is meaningless, but perms are OK.
169 	 */
170 	if (fileAttrs & RPMFILE_GHOST) {
171 	    metamode &= ~S_IFMT;
172 	    filemode &= ~S_IFMT;
173 	}
174 
175 	if (metamode != filemode)
176 	    vfy |= RPMVERIFY_MODE;
177 
178 #if WITH_ACL
179 	/*
180 	 * For now, any non-default acl's on a file is a difference as rpm
181 	 * cannot have set them.
182 	 */
183 	acl_t facl = acl_get_file(fn, ACL_TYPE_ACCESS);
184 	if (facl) {
185 	    if (acl_equiv_mode(facl, NULL) == 1) {
186 		vfy |= RPMVERIFY_MODE;
187 	    }
188 	    acl_free(facl);
189 	}
190 #endif
191     }
192 
193     if (flags & RPMVERIFY_RDEV) {
194 	if (S_ISCHR(fsb.st_mode) != S_ISCHR(sb.st_mode)
195 	 || S_ISBLK(fsb.st_mode) != S_ISBLK(sb.st_mode))
196 	{
197 	    vfy |= RPMVERIFY_RDEV;
198 	} else if (S_ISDEV(fsb.st_mode) && S_ISDEV(sb.st_mode)) {
199 	    rpm_rdev_t st_rdev = (sb.st_rdev & 0xffff);
200 	    rpm_rdev_t frdev = (fsb.st_rdev & 0xffff);
201 	    if (st_rdev != frdev)
202 		vfy |= RPMVERIFY_RDEV;
203 	}
204     }
205 
206 #if WITH_CAP
207     if (flags & RPMVERIFY_CAPS) {
208 	cap_t cap = NULL;
209 	cap_t fcap = cap_get_file(fn);
210 	const char *captext = rpmfilesFCaps(fi, ix);
211 
212 	/* captext "" means no capability */
213 	if (captext && captext[0])
214 	    cap = cap_from_text(captext);
215 
216 	if ((fcap || cap) && (cap_compare(cap, fcap) != 0))
217 	    vfy |= RPMVERIFY_CAPS;
218 
219 	cap_free(fcap);
220 	cap_free(cap);
221     }
222 #endif
223 
224     if ((flags & RPMVERIFY_MTIME) && (sb.st_mtime != fsb.st_mtime))
225 	vfy |= RPMVERIFY_MTIME;
226 
227     if ((flags & RPMVERIFY_USER) && (sb.st_uid != fsb.st_uid))
228 	vfy |= RPMVERIFY_USER;
229 
230     if ((flags & RPMVERIFY_GROUP) && (sb.st_gid != fsb.st_gid))
231 	vfy |= RPMVERIFY_GROUP;
232 
233 exit:
234     free(fn);
235     return vfy;
236 }
237 
238 /**
239  * Return exit code from running verify script from header.
240  * @param ts		transaction set
241  * @param h		header
242  * @return              0 on success
243  */
rpmVerifyScript(rpmts ts,Header h)244 static int rpmVerifyScript(rpmts ts, Header h)
245 {
246     int rc = 0;
247 
248     if (headerIsEntry(h, RPMTAG_VERIFYSCRIPT)) {
249 	/* fake up a transaction element */
250 	rpmte p = rpmteNew(ts, h, TR_RPMDB, NULL, NULL, 0);
251 
252 	if (p != NULL) {
253 	    rpmteSetHeader(p, h);
254 
255 	    rc = (rpmpsmRun(ts, p, PKG_VERIFY) != RPMRC_OK);
256 
257 	    /* clean up our fake transaction bits */
258 	    rpmteFree(p);
259 	} else {
260 	    rc = RPMRC_FAIL;
261 	}
262     }
263 
264     return rc;
265 }
266 
267 #define unknown "?"
268 #define	_verify(_RPMVERIFY_F, _C, _pad)	\
269 	((verifyResult & _RPMVERIFY_F) ? _C : _pad)
270 #define	_verifylink(_RPMVERIFY_F, _C, _pad)	\
271 	((verifyResult & RPMVERIFY_READLINKFAIL) ? unknown : \
272 	 (verifyResult & _RPMVERIFY_F) ? _C : _pad)
273 #define	_verifyfile(_RPMVERIFY_F, _C, _pad)	\
274 	((verifyResult & RPMVERIFY_READFAIL) ? unknown : \
275 	 (verifyResult & _RPMVERIFY_F) ? _C : _pad)
rpmVerifyString(uint32_t verifyResult,const char * pad)276 char * rpmVerifyString(uint32_t verifyResult, const char *pad)
277 {
278     char *fmt = NULL;
279     rasprintf(&fmt, "%s%s%s%s%s%s%s%s%s",
280 		_verify(RPMVERIFY_FILESIZE, "S", pad),
281 		_verify(RPMVERIFY_MODE, "M", pad),
282 		_verifyfile(RPMVERIFY_FILEDIGEST, "5", pad),
283 		_verify(RPMVERIFY_RDEV, "D", pad),
284 		_verifylink(RPMVERIFY_LINKTO, "L", pad),
285 		_verify(RPMVERIFY_USER, "U", pad),
286 		_verify(RPMVERIFY_GROUP, "G", pad),
287 		_verify(RPMVERIFY_MTIME, "T", pad),
288 		_verify(RPMVERIFY_CAPS, "P", pad));
289 
290     return fmt;
291 }
292 #undef _verifyfile
293 #undef _verifylink
294 #undef _verify
295 #undef aok
296 #undef unknown
297 
rpmFFlagsString(uint32_t fflags,const char * pad)298 char * rpmFFlagsString(uint32_t fflags, const char *pad)
299 {
300     char *fmt = NULL;
301     rasprintf(&fmt, "%s%s%s%s%s%s%s%s%s",
302 		(fflags & RPMFILE_DOC) ? "d" : pad,
303 		(fflags & RPMFILE_CONFIG) ? "c" : pad,
304 		(fflags & RPMFILE_SPECFILE) ? "s" : pad,
305 		(fflags & RPMFILE_MISSINGOK) ? "m" : pad,
306 		(fflags & RPMFILE_NOREPLACE) ? "n" : pad,
307 		(fflags & RPMFILE_GHOST) ? "g" : pad,
308 		(fflags & RPMFILE_LICENSE) ? "l" : pad,
309 		(fflags & RPMFILE_README) ? "r" : pad,
310 		(fflags & RPMFILE_ARTIFACT) ? "a" : pad);
311     return fmt;
312 }
313 
stateStr(rpmfileState fstate)314 static const char * stateStr(rpmfileState fstate)
315 {
316     switch (fstate) {
317     case RPMFILE_STATE_NORMAL:
318 	return NULL;
319     case RPMFILE_STATE_NOTINSTALLED:
320 	return rpmIsVerbose() ? _("not installed") : NULL;
321     case RPMFILE_STATE_NETSHARED:
322 	return rpmIsVerbose() ? _("net shared") : NULL;
323     case RPMFILE_STATE_WRONGCOLOR:
324 	return rpmIsVerbose() ? _("wrong color") : NULL;
325     case RPMFILE_STATE_REPLACED:
326 	return _("replaced");
327     case RPMFILE_STATE_MISSING:
328 	return _("no state");
329     }
330     return _("unknown state");
331 }
332 
333 /**
334  * Check file info from header against what's actually installed.
335  * @param ts		transaction set
336  * @param h		header to verify
337  * @param omitMask	bits to disable verify checks
338  * @param incAttr	skip files without these attrs (eg %ghost)
339  * @param skipAttr	skip files with these attrs (eg %ghost)
340  * @return		0 no problems, 1 problems found
341  */
verifyHeader(rpmts ts,Header h,rpmVerifyAttrs omitMask,rpmfileAttrs incAttrs,rpmfileAttrs skipAttrs)342 static int verifyHeader(rpmts ts, Header h, rpmVerifyAttrs omitMask,
343 			rpmfileAttrs incAttrs, rpmfileAttrs skipAttrs)
344 {
345     rpmVerifyAttrs verifyResult = 0;
346     rpmVerifyAttrs verifyAll = 0; /* assume no problems */
347     rpmfi fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, RPMFI_FLAGS_VERIFY);
348 
349     if (fi == NULL)
350 	return 1;
351 
352     rpmfiInit(fi, 0);
353     while (rpmfiNext(fi) >= 0) {
354 	rpmfileAttrs fileAttrs = rpmfiFFlags(fi);
355 	char *buf = NULL, *attrFormat;
356 	const char *fstate = NULL;
357 	char ac;
358 
359 	/* If filtering by inclusion, skip non-matching (eg --configfiles) */
360 	if (incAttrs && !(incAttrs & fileAttrs))
361 	    continue;
362 
363 	/* Skip on attributes (eg from --noghost) */
364 	if (skipAttrs & fileAttrs)
365 	    continue;
366 
367 	verifyResult = rpmfiVerify(fi, omitMask);
368 
369 	/* Filter out timestamp differences of shared files */
370 	if (verifyResult & RPMVERIFY_MTIME) {
371 	    rpmdbMatchIterator mi;
372 	    mi = rpmtsInitIterator(ts, RPMDBI_BASENAMES, rpmfiFN(fi), 0);
373 	    if (rpmdbGetIteratorCount(mi) > 1)
374 		verifyResult &= ~RPMVERIFY_MTIME;
375 	    rpmdbFreeIterator(mi);
376 	}
377 
378 	/* State is only meaningful for installed packages */
379 	if (headerGetInstance(h))
380 	    fstate = stateStr(rpmfiFState(fi));
381 
382 	attrFormat = rpmFFlagsString(fileAttrs, "");
383 	ac = rstreq(attrFormat, "") ? ' ' : attrFormat[0];
384 	if (verifyResult & RPMVERIFY_LSTATFAIL) {
385 	    if (!(fileAttrs & (RPMFILE_MISSINGOK|RPMFILE_GHOST)) || rpmIsVerbose()) {
386 		rasprintf(&buf, _("missing   %c %s"), ac, rpmfiFN(fi));
387 		if ((verifyResult & RPMVERIFY_LSTATFAIL) != 0 &&
388 		    errno != ENOENT) {
389 		    char *app;
390 		    rasprintf(&app, " (%s)", strerror(errno));
391 		    rstrcat(&buf, app);
392 		    free(app);
393 		}
394 	    }
395 	} else if (verifyResult || fstate || rpmIsVerbose()) {
396 	    char *verifyFormat = rpmVerifyString(verifyResult, ".");
397 	    rasprintf(&buf, "%s  %c %s", verifyFormat, ac, rpmfiFN(fi));
398 	    free(verifyFormat);
399 	}
400 	free(attrFormat);
401 
402 	if (buf) {
403 	    if (fstate)
404 		buf = rstrscat(&buf, " (", fstate, ")", NULL);
405 	    rpmlog(RPMLOG_NOTICE, "%s\n", buf);
406 	    buf = _free(buf);
407 	}
408 
409 	/* Filter out missing %ghost/%missingok errors from final result */
410 	if (fileAttrs & (RPMFILE_MISSINGOK|RPMFILE_GHOST))
411 	    verifyResult &= ~RPMVERIFY_LSTATFAIL;
412 
413 	verifyAll |= verifyResult;
414     }
415     rpmfiFree(fi);
416 
417     return (verifyAll != 0) ? 1 : 0;
418 }
419 
420 /**
421  * Check installed package dependencies for problems.
422  * @param ts		transaction set
423  * @param h		header
424  * @return		number of problems found (0 for no problems)
425  */
verifyDependencies(rpmts ts,Header h)426 static int verifyDependencies(rpmts ts, Header h)
427 {
428     rpmps ps;
429     rpmte te;
430     int rc;
431 
432     rpmtsEmpty(ts);
433     (void) rpmtsAddInstallElement(ts, h, NULL, 0, NULL);
434 
435     (void) rpmtsCheck(ts);
436     te = rpmtsElement(ts, 0);
437     ps = rpmteProblems(te);
438     rc = rpmpsNumProblems(ps);
439 
440     if (rc > 0) {
441 	rpmlog(RPMLOG_NOTICE, _("Unsatisfied dependencies for %s:\n"),
442 	       rpmteNEVRA(te));
443 	rpmpsi psi = rpmpsInitIterator(ps);
444 	rpmProblem p;
445 
446 	while ((p = rpmpsiNext(psi)) != NULL) {
447 	    char * ps = rpmProblemString(p);
448 	    rpmlog(RPMLOG_NOTICE, "\t%s\n", ps);
449 	    free(ps);
450 	}
451 	rpmpsFreeIterator(psi);
452     }
453     rpmpsFree(ps);
454     rpmtsEmpty(ts);
455 
456     return rc;
457 }
458 
showVerifyPackage(QVA_t qva,rpmts ts,Header h)459 int showVerifyPackage(QVA_t qva, rpmts ts, Header h)
460 {
461     int ec = 0;
462     int rc;
463 
464     if (qva->qva_flags & VERIFY_DEPS) {
465 	if ((rc = verifyDependencies(ts, h)) != 0)
466 	    ec = rc;
467     }
468     if (qva->qva_flags & VERIFY_FILES) {
469 	if ((rc = verifyHeader(ts, h, qva->qva_ofvattr,
470 				qva->qva_incattr, qva->qva_excattr)) != 0)
471 	    ec = rc;
472     }
473     if (qva->qva_flags & VERIFY_SCRIPT) {
474 	if ((rc = rpmVerifyScript(ts, h)) != 0)
475 	    ec = rc;
476     }
477 
478     return ec;
479 }
480 
rpmcliVerify(rpmts ts,QVA_t qva,char * const * argv)481 int rpmcliVerify(rpmts ts, QVA_t qva, char * const * argv)
482 {
483     rpmVSFlags vsflags, ovsflags;
484     int ec = 0;
485     FD_t scriptFd = fdDup(STDOUT_FILENO);
486 
487     /*
488      * Open the DB + indices explicitly before possible chroot,
489      * otherwises BDB is going to be unhappy...
490      */
491     rpmtsOpenDB(ts, O_RDONLY);
492     rpmdbOpenAll(rpmtsGetRdb(ts));
493     if (rpmChrootSet(rpmtsRootDir(ts)) || rpmChrootIn()) {
494 	ec = 1;
495 	goto exit;
496     }
497 
498     if (qva->qva_showPackage == NULL)
499         qva->qva_showPackage = showVerifyPackage;
500 
501     vsflags = rpmExpandNumeric("%{?_vsflags_verify}");
502     vsflags |= rpmcliVSFlags;
503     vsflags &= ~RPMVSF_NEEDPAYLOAD;
504 
505     rpmtsSetScriptFd(ts, scriptFd);
506     ovsflags = rpmtsSetVSFlags(ts, vsflags);
507     ec = rpmcliArgIter(ts, qva, argv);
508     rpmtsSetVSFlags(ts, ovsflags);
509     rpmtsSetScriptFd(ts, NULL);
510 
511     if (qva->qva_showPackage == showVerifyPackage)
512         qva->qva_showPackage = NULL;
513 
514     rpmtsEmpty(ts);
515 
516     if (rpmChrootOut() || rpmChrootSet(NULL))
517 	ec = 1;
518 
519 exit:
520     Fclose(scriptFd);
521 
522     return ec;
523 }
524