/
githubmirror
/
rpm
Обзор
Документация
Войти
/
githubmirror
/
rpm
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
lib/fprint.cc
482 строки
13 KB
Panu Matilainen
Fix a relocation related crash in fingerprinting
13 окт 2025, 19:04
13 окт 2025, 19:04
04c7e4a
Код
Авторство
О чём код?
/** * \file lib/fprint.c */ #include "system.h" #include <map> #include <unordered_map> #include <rpm/rpmfileutil.h> /* for rpmCleanPath */ #include <rpm/rpmstring.h> #include <rpm/rpmts.h> #include <rpm/rpmsq.h> #include "rpmdb_internal.hh" #include "rpmfi_internal.hh" #include "rpmte_internal.hh" #include "fprint.hh" #include "misc.hh" #include "debug.h" #include <libgen.h> /** * Finger print cache entry. * This is really a directory and symlink cache. We don't differentiate between * the two. We can prepopulate it, which allows us to easily conduct "fake" * installs of a system w/o actually mounting filesystems. */ struct fprintCacheEntry_s { rpmsid dirId; /*!< path to existing directory */ dev_t dev; /*!< stat(2) device number */ ino_t ino; /*!< stat(2) inode number */ }; /** * Associates a trailing sub-directory and final base name with an existing * directory finger print. */ struct fingerPrint { /*! directory finger print entry (the directory path is stat(2)-able */ const struct fprintCacheEntry_s * entry; /*! trailing sub-directory path (directories that are not stat(2)-able */ rpmsid subDirId; rpmsid baseNameId; /*!< file base name id */ }; #define FP_ENTRY_EQUAL(a, b) (((a)->dev == (b)->dev) && ((a)->ino == (b)->ino)) #define FP_EQUAL(a, b) ( \ FP_ENTRY_EQUAL((a).entry, (b).entry) && \ ((a).baseNameId == (b).baseNameId) && \ ((a).subDirId == (b).subDirId) \ ) struct fpLess { bool operator()(const fingerPrint * k1, const fingerPrint * k2) const { /* An item cannot be less than itself */ if (k1 == k2) return false; if (FP_ENTRY_EQUAL(k1->entry, k2->entry)) { if (k1->subDirId == k2->subDirId) return k1->baseNameId < k2->baseNameId; return k1->subDirId < k2->subDirId; } if (k1->entry->dev == k2->entry->dev) return k1->entry->ino < k2->entry->ino; return k1->entry->dev < k2->entry->dev; } }; using rpmFpEntryHash = std::unordered_multimap<rpmsid,fprintCacheEntry_s>; using rpmFpHash = std::multimap<fingerPrint *,rpmffi_s,fpLess>; /** * Finger print cache. */ struct fprintCache_s { rpmFpEntryHash ht; /*!< hashed by dirName */ rpmFpHash fp; /*!< hashed by fingerprint */ rpmstrPool pool; /*!< string pool */ }; fingerPrintCache fpCacheCreate(int sizeHint, rpmstrPool pool) { fingerPrintCache fpc = new fprintCache_s {}; fpc->pool = (pool != NULL) ? rpmstrPoolLink(pool) : rpmstrPoolCreate(); return fpc; } fingerPrintCache fpCacheFree(fingerPrintCache cache) { if (cache) { cache->pool = rpmstrPoolFree(cache->pool); delete cache; } return NULL; } /** * Find directory name entry in cache. * @param cache pointer to fingerprint cache * @param dirId string id to locate in cache * @return pointer to directory name entry (or NULL if not found). */ static const struct fprintCacheEntry_s * cacheContainsDirectory( fingerPrintCache cache, rpmsid dirId) { auto entry = cache->ht.find(dirId); if (entry != cache->ht.end()) return &entry->second; return NULL; } static char * canonDir(rpmstrPool pool, rpmsid dirNameId) { const char * dirName = rpmstrPoolStr(pool, dirNameId); char *cdnbuf = NULL; if (*dirName == '/') { cdnbuf = xstrdup(dirName); } else { /* Using realpath on the arg isn't correct if the arg is a symlink, * especially if the symlink is a dangling link. What we * do instead is use realpath() on `.' and then append arg to * the result. */ /* if the current directory doesn't exist, we might fail. oh well. */ if ((cdnbuf = realpath(".", NULL)) != NULL) cdnbuf = rstrscat(&cdnbuf, "/", dirName, NULL); } /* ensure canonical path with a trailing slash */ if (cdnbuf) { size_t cdnl = strlen(cdnbuf); if (cdnl > 1) { cdnbuf = rpmCleanPath(cdnbuf); cdnbuf = rstrcat(&cdnbuf, "/"); } } return cdnbuf; } static int doLookupId(fingerPrintCache cache, rpmsid dirNameId, rpmsid baseNameId, fingerPrint *fp) { struct stat sb; const struct fprintCacheEntry_s * cacheHit; char *cdn = canonDir(cache->pool, dirNameId); rpmsid fpId; size_t fpLen; if (cdn == NULL) goto exit; /* XXX only if realpath() above fails */ memset(fp, 0, sizeof(*fp)); fpId = rpmstrPoolId(cache->pool, cdn, 1); fpLen = rpmstrPoolStrlen(cache->pool, fpId);; while (1) { /* as we're stating paths here, we want to follow symlinks */ cacheHit = cacheContainsDirectory(cache, fpId); if (cacheHit != NULL) { fp->entry = cacheHit; } else if (!stat(rpmstrPoolStr(cache->pool, fpId), &sb)) { struct fprintCacheEntry_s newEntry = { .dirId = fpId, .dev = sb.st_dev, .ino = sb.st_ino, }; auto ret = cache->ht.insert({fpId, newEntry}); fp->entry = &ret->second; } if (fp->entry) { const char * subDir = cdn + fpLen - 1; /* XXX don't bother saving '/' as subdir */ if (subDir[0] == '\0' || (subDir[0] == '/' && subDir[1] == '\0')) subDir = NULL; fp->baseNameId = baseNameId; if (subDir != NULL) fp->subDirId = rpmstrPoolId(cache->pool, subDir, 1); goto exit; } /* stat of '/' just failed! */ if (fpLen == 1) abort(); /* Find the parent directory and its id for the next round */ fpLen--; while (fpLen > 1 && cdn[fpLen-1] != '/') fpLen--; fpId = rpmstrPoolIdn(cache->pool, cdn, fpLen, 1); } exit: free(cdn); /* XXX TODO: failure from eg realpath() should be returned and handled */ return 0; } static int doLookup(fingerPrintCache cache, const char * dirName, const char * baseName, fingerPrint *fp) { rpmsid dirNameId = rpmstrPoolId(cache->pool, dirName, 1); rpmsid baseNameId = rpmstrPoolId(cache->pool, baseName, 1); return doLookupId(cache, dirNameId, baseNameId, fp); } int fpLookup(fingerPrintCache cache, const char * dirName, const char * baseName, fingerPrint **fp) { if (*fp == NULL) *fp = (fingerPrint *)xcalloc(1, sizeof(**fp)); return doLookup(cache, dirName, baseName, *fp); } int fpLookupId(fingerPrintCache cache, rpmsid dirNameId, rpmsid baseNameId, fingerPrint **fp) { if (*fp == NULL) *fp = (fingerPrint *)xcalloc(1, sizeof(**fp)); return doLookupId(cache, dirNameId, baseNameId, *fp); } const char * fpEntryDir(fingerPrintCache cache, fingerPrint *fp) { const char * dn = NULL; if (fp && fp->entry) dn = rpmstrPoolStr(cache->pool, fp->entry->dirId); return dn; } dev_t fpEntryDev(fingerPrintCache cache, fingerPrint *fp) { return (fp && fp->entry) ? fp->entry->dev : 0; } int fpLookupEquals(fingerPrintCache cache, fingerPrint *fp, const char * dirName, const char * baseName) { struct fingerPrint ofp; doLookup(cache, dirName, baseName, &ofp); return FP_EQUAL(*fp, ofp); } int fpLookupEqualsId(fingerPrintCache cache, fingerPrint *fp, rpmsid dirNameId, rpmsid baseNameId) { struct fingerPrint ofp; doLookupId(cache, dirNameId, baseNameId, &ofp); return FP_EQUAL(*fp, ofp); } fingerPrint * fpLookupList(fingerPrintCache cache, rpmstrPool pool, rpmsid * dirNames, rpmsid * baseNames, const uint32_t * dirIndexes, int fileCount) { fingerPrint * fps = (fingerPrint *)xcalloc(fileCount, sizeof(*fps)); int i; /* * We could handle different pools easily enough, but there should be * no need for that. Make sure we catch any oddball cases there might be * lurking about. */ assert(cache->pool == pool); for (i = 0; i < fileCount; i++) { /* If this is in the same directory as the last file, don't bother redoing all of this work */ if (i > 0 && dirIndexes[i - 1] == dirIndexes[i]) { fps[i].entry = fps[i - 1].entry; fps[i].subDirId = fps[i - 1].subDirId; /* XXX if the pools are different, copy would be needed */ fps[i].baseNameId = baseNames[i]; } else { doLookupId(cache, dirNames[dirIndexes[i]], baseNames[i], &fps[i]); } } return fps; } /* Check file for to be installed symlinks in their path and correct their fp */ static void fpLookupSubdir(rpmFpHash & symlinks, fingerPrintCache fpc, fingerPrint *fp) { struct fingerPrint current_fp; const char *currentsubdir; size_t lensubDir, bnStart, bnEnd; int symlinkcount = 0; for (;;) { int found = 0; if (fp->subDirId == 0) break; /* directory exists - no need to look for symlinks */ currentsubdir = rpmstrPoolStr(fpc->pool, fp->subDirId); lensubDir = rpmstrPoolStrlen(fpc->pool, fp->subDirId); current_fp = *fp; /* Set baseName to the upper most dir */ bnStart = bnEnd = 1; while (bnEnd < lensubDir && currentsubdir[bnEnd] != '/') bnEnd++; /* no subDir for now */ current_fp.subDirId = 0; while (bnEnd < lensubDir) { current_fp.baseNameId = rpmstrPoolIdn(fpc->pool, currentsubdir + bnStart, bnEnd - bnStart, 1); auto range = symlinks.equal_range(¤t_fp); for (auto it = range.first; it != range.second; ++it) { const rpmffi_s & rec = it->second; rpmfiles foundfi = rpmteFiles(rec.p); char const *linktarget = rpmfilesFLink(foundfi, rec.fileno); char *link; rpmsid linkId; foundfi = rpmfilesFree(foundfi); if (!linktarget || *linktarget == '\0') continue; /* this "directory" will be symlink */ link = NULL; if (*linktarget != '/') { const char *dn, *subDir = NULL; dn = rpmstrPoolStr(fpc->pool, current_fp.entry->dirId); if (current_fp.subDirId) { subDir = rpmstrPoolStr(fpc->pool, current_fp.subDirId); } rstrscat(&link, dn, subDir ? subDir : "", "/", NULL); } rstrscat(&link, linktarget, "/", NULL); if (currentsubdir[bnEnd]) rstrscat(&link, currentsubdir + bnEnd, NULL); linkId = rpmstrPoolId(fpc->pool, link, 1); free(link); /* this modifies the fingerprint! */ doLookupId(fpc, linkId, fp->baseNameId, fp); found = 1; break; } if (found) break; /* Set former baseName as subDir */ bnEnd++; current_fp.subDirId = rpmstrPoolIdn(fpc->pool, currentsubdir, bnEnd, 1); /* set baseName to the next lower dir */ bnStart = bnEnd; while (bnEnd < lensubDir && currentsubdir[bnEnd] != '/') bnEnd++; } if (!found) break; /* no symlink found, we are done */ if (++symlinkcount > 50) { /* we followed too many symlinks, there is most likely a cycle */ /* TODO: warning/error */ break; } } } fingerPrint * fpCacheGetByFp(fingerPrintCache cache, struct fingerPrint * fp, int ix, std::vector<struct rpmffi_s> & recs) { fingerPrint *fpe = fp + ix; if (fpe->entry) { auto range = cache->fp.equal_range(fpe); if (range.first != range.second) { for (auto it = range.first; it != range.second; ++it) recs.push_back(it->second); return fpe; } } return NULL; } void fpCachePopulate(fingerPrintCache fpc, rpmts ts, int fileCount) { rpmtsi pi; rpmte p; rpmfs fs; rpmfiles fi; int i, fc; int havesymlinks = 0; rpmFpHash symlinks; /* populate the fingerprints of all packages in the transaction */ /* also create a hash of all symlinks in the new packages */ pi = rpmtsiInit(ts); while ((p = rpmtsiNext(pi, 0)) != NULL) { if ((fi = rpmteFiles(p)) == NULL) continue; (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0); rpmfilesFpLookup(fi, fpc); fs = rpmteGetFileStates(p); fc = rpmfsFC(fs); if (rpmteType(p) != TR_REMOVED) { fingerPrint *fpList = rpmfilesFps(fi); /* collect symbolic links */ for (i = 0; i < fc; i++) { struct rpmffi_s ffi; char const *linktarget; if (XFA_SKIPPING(rpmfsGetAction(fs, i))) continue; linktarget = rpmfilesFLink(fi, i); if (!(linktarget && *linktarget != '\0')) continue; ffi.p = p; ffi.fileno = i; fingerPrint *fp = fpList + i; if (fp->entry) symlinks.insert({fp, ffi}); havesymlinks = 1; } } (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), fc); rpmfilesFree(fi); } rpmtsiFree(pi); /* =============================================== * Create the fingerprint -> (p, fileno) hash table * Also adapt the fingerprint if we have symlinks */ pi = rpmtsiInit(ts); while ((p = rpmtsiNext(pi, 0)) != NULL) { fingerPrint *fpList, *lastfp = NULL; if ((fi = rpmteFiles(p)) == NULL) continue; fs = rpmteGetFileStates(p); fc = rpmfsFC(fs); fpList = rpmfilesFps(fi); (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0); for (i = 0; i < fc; i++) { struct rpmffi_s ffi; if (XFA_SKIPPING(rpmfsGetAction(fs, i))) continue; if (havesymlinks) { /* if the entry/subdirid matches the one from the * last entry we do not need to call fpLookupSubdir */ if (!lastfp || lastfp->entry != fpList[i].entry || lastfp->subDirId != fpList[i].subDirId) fpLookupSubdir(symlinks, fpc, fpList + i); } ffi.p = p; ffi.fileno = i; lastfp = fpList + i; if (lastfp->entry) fpc->fp.insert({lastfp, ffi}); } (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0); rpmfilesFree(fi); } rpmtsiFree(pi); }