pkgdb

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: BSD-3-Clause Imports: 12 Imported by: 0

Documentation

Overview

Package pkgdb reads the package databases a system or container image carries (dpkg, apk, rpm) and answers which installed package owns a file, so a PHP binary installed by a distribution is judged by that distribution's fix and not by its upstream version string.

Index

Constants

This section is empty.

Variables

View Source
var ErrLinkLoop = errors.New("too many levels of symbolic links")

ErrLinkLoop is returned when resolving a path needs more than 40 symlink expansions.

Functions

func OSRelease

func OSRelease(files map[string][]byte) (knowledge.OSRelease, bool)

OSRelease reads the distribution identity: /etc/os-release, or /usr/lib/os-release when /etc/os-release is absent from files (in images it is usually a symlink to ../usr/lib/os-release, so the walk only collects the target). Without either, it falls back to /etc/alpine-release (ID alpine) and /etc/debian_version (ID debian; a numeric "12.7" gives VERSION_ID "12", "trixie/sid" gives the codename). It returns false when nothing identifies the system.

func RegisterRPMSQLiteReader

func RegisterRPMSQLiteReader(r RPMSQLiteReader)

RegisterRPMSQLiteReader installs the reader for rpm sqlite databases. The package github.com/DefectDojo/earlylight/detectors/php2shell/internal/pkgdb/rpmdb registers one from init(); blank-import it to read rpm 4.16+ databases (RHEL 9, Fedora, Amazon Linux 2023, SLES 16). Without it, Build records a problem for every sqlite database it finds. Berkeley DB and ndb databases need no registration.

func Wanted

func Wanted(p string) bool

Wanted reports whether an absolute path (within the scanned root) is a file Build or OSRelease needs: package databases, dpkg file lists and digests, and release files. The caller collects the content of every wanted regular file (path -> bytes) and passes the map to Build and OSRelease; symlinks and hard links go to a Resolver instead.

Types

type File

type File struct {
	Path string
	// Algo is "md5", "sha1", "sha256" (or another name rpm uses); empty when the
	// database records no digest for the file.
	Algo   string
	Digest []byte
}

File is one file a package installed, with the digest the database records for it, if any.

type Format

type Format string

Format names a package manager.

const (
	Dpkg Format = "dpkg"
	APK  Format = "apk"
	RPM  Format = "rpm"
)

type Index

type Index struct {

	// DBs lists the databases that were read.
	DBs []string
	// Problems lists databases that exist but could not be read fully. A file
	// under such a database may have an owner this index does not know.
	Problems []string
	// contains filtered or unexported fields
}

Index maps absolute paths to the packages that own them, across every database found.

func Build

func Build(files map[string][]byte) *Index

Build parses the collected files (absolute path -> content) into an Index. It never fails: databases that exist but cannot be read completely are recorded in Index.Problems with the reason, and every database read is in Index.DBs. A package read from a database with a problem is still indexed when its own record was readable.

func NewIndex

func NewIndex(pkgs []Installed, dbs, problems []string) *Index

NewIndex builds an index from installed packages.

func (*Index) Empty

func (ix *Index) Empty() bool

Empty reports whether no package database was found at all.

func (*Index) Owners

func (ix *Index) Owners(p string) []Owner

Owners returns every package that lists p. Callers resolve symlinked directories first (see Resolver).

func (*Index) OwnersOf

func (ix *Index) OwnersOf(r *Resolver, p string) []Owner

OwnersOf returns the owners of p under every alias the resolver knows (see Resolver.Aliases), deduplicated. With a nil resolver it equals Owners(p).

func (*Index) Packages

func (ix *Index) Packages() []Installed

Packages returns every installed package read, including packages whose database records no file list (distroless status.d entries without .md5sums, for example): those have no Files. Callers can thus tell "a PHP package is installed but its files are unknown" from "no package owns this file". The slice is shared; do not modify it.

type Installed

type Installed struct {
	Package Package
	Files   []File
}

Installed is a package and the files it owns.

type Owner

type Owner struct {
	Package Package
	File    File
}

Owner is a package that lists a file, with the digest it recorded.

type Package

type Package struct {
	Format Format `json:"format"`
	Name   string `json:"name"`
	// Version is the full version as the package manager writes it, with the
	// epoch if it has one: "1:8.2.35-1~deb12u1", "8.3.29-r0", "8.2.13-1.module+el9.4.0+20xxx".
	// For rpm it is EPOCH:VERSION-RELEASE (epoch omitted when zero).
	Version string `json:"version"`
	// Source is the source package it was built from: dpkg "Source:", apk origin,
	// rpm source rpm name without version. Equals Name when the database has none.
	Source string `json:"source"`
	// Vendor is rpm's Vendor tag, or dpkg's Maintainer. Empty for apk.
	Vendor string `json:"vendor,omitempty"`
	Arch   string `json:"arch,omitempty"`
	// DB is the database file the package was read from.
	DB string `json:"db"`
}

Package is one installed package.

type RPMRecord

type RPMRecord struct {
	Key  uint32
	Blob []byte
}

RPMRecord is one row of an rpm sqlite database's Packages table: the package's instance number (hnum) and its header blob.

type RPMSQLiteReader

type RPMSQLiteReader func(main, wal []byte) ([]RPMRecord, error)

RPMSQLiteReader returns every row of the Packages table of an rpm sqlite database (rpmdb.sqlite), applying the write-ahead log wal when non-nil. It must fail rather than return a partial table.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver knows the symbolic and hard links of a scanned filesystem and maps a path to the other paths that name the same file, so a file found at /usr/bin/bash is matched with a package database that lists /bin/bash (merged /usr), and a hard-linked copy is matched with the name a package lists.

How to feed it, during the single walk over the filesystem or image tar:

  • every symbolic link: AddSymlink(abs, target), where abs is the link's absolute path within the scanned root ("/bin", "/etc/os-release") and target is the link text exactly as stored ("usr/bin", "../usr/lib/os-release", "/usr/bin/php8.2"). Relative targets are relative to the link's directory; absolute targets are interpreted inside the scanned root (chroot semantics), never on the host;
  • every tar hard link entry: AddHardlink(abs, target), with target the entry's Linkname made absolute ("usr/bin/php8.2" -> "/usr/bin/php8.2"). For a directory walk, pass files sharing (device, inode) the same way.

For an image, feed the flattened (whiteouts applied) view: a link removed by a later layer must not be added. Paths are cleaned; a link added twice keeps the last target. Resolution is bounded: at most maxLinkHops symlink expansions per lookup (Linux allows 40), so loops end with an error.

A Resolver may be read (Canonical, Resolve, Aliases) from several goroutines once it is fully fed; adding links concurrently with reads is not supported.

func NewResolver

func NewResolver(symlinks map[string]string) *Resolver

NewResolver returns a resolver for the given symlinks (absolute path -> target as written). The map may be nil; more links can be added later.

func (r *Resolver) AddHardlink(abs, target string)

AddHardlink records that abs is a hard link to target (both name one inode).

func (r *Resolver) AddSymlink(abs, target string)

AddSymlink records a symbolic link.

func (*Resolver) Aliases

func (r *Resolver) Aliases(p string) []string

Aliases returns every path that names the same file as p through symlinked parent directories and hard links: p itself, its canonical path, and each path obtained by replacing a directory prefix with a symlink that resolves to it, or the file with a member of its hard link group. A symlink to the file itself is not an alias (it is a different directory entry). The result is sorted, deduplicated and bounded to 256 paths. p is expected clean and absolute, as a walk produces it; a path with "." or ".." components that cross symlinks is first resolved physically.

func (*Resolver) Canonical

func (r *Resolver) Canonical(p string) (string, error)

Canonical resolves symlinks in the directories of p (not its last component) and returns the cleaned absolute path of the directory entry p names. "/bin/bash" becomes "/usr/bin/bash" when /bin -> usr/bin.

func (*Resolver) Resolve

func (r *Resolver) Resolve(p string) (string, error)

Resolve is Canonical that also follows a symlink in the last component, so it returns the path of the file p finally refers to ("/usr/bin/php" -> "/usr/bin/php8.2" through /etc/alternatives). A dangling link resolves to the missing target's path.

Directories

Path Synopsis
Package rpmdb lets pkgdb read rpm sqlite databases (rpmdb.sqlite, rpm 4.16 and later: RHEL 9, Fedora 33+, Amazon Linux 2023, SLES 16).
Package rpmdb lets pkgdb read rpm sqlite databases (rpmdb.sqlite, rpm 4.16 and later: RHEL 9, Fedora 33+, Amazon Linux 2023, SLES 16).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL