Merge pull request #20874 from mheon/eval_symlinks_on_db_paths

Handle symlinks when checking DB vs runtime configs
This commit is contained in:
openshift-merge-bot[bot]
2023-12-03 11:50:03 +00:00
committed by GitHub
3 changed files with 89 additions and 21 deletions

View File

@ -4,7 +4,9 @@
package libpod
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"runtime"
@ -93,6 +95,7 @@ type dbConfigValidation struct {
runtimeValue string
key []byte
defaultValue string
isPath bool
}
// Check if the configuration of the database is compatible with the
@ -111,42 +114,49 @@ func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
runtime.GOOS,
osKey,
runtime.GOOS,
false,
},
{
"libpod root directory (staticdir)",
filepath.Clean(rt.config.Engine.StaticDir),
staticDirKey,
"",
true,
},
{
"libpod temporary files directory (tmpdir)",
filepath.Clean(rt.config.Engine.TmpDir),
tmpDirKey,
"",
true,
},
{
"storage temporary directory (runroot)",
filepath.Clean(rt.StorageConfig().RunRoot),
runRootKey,
storeOpts.RunRoot,
true,
},
{
"storage graph root directory (graphroot)",
filepath.Clean(rt.StorageConfig().GraphRoot),
graphRootKey,
storeOpts.GraphRoot,
true,
},
{
"storage graph driver",
rt.StorageConfig().GraphDriverName,
graphDriverKey,
storeOpts.GraphDriverName,
false,
},
{
"volume path",
rt.config.Engine.VolumePath,
volPathKey,
"",
true,
},
}
@ -221,22 +231,45 @@ func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bo
}
dbValue := string(keyBytes)
ourValue := toCheck.runtimeValue
if toCheck.runtimeValue != dbValue {
// Tolerate symlinks when possible - most relevant for OStree systems
// and rootless containers, where we want to put containers in /home,
// which is symlinked to /var/home.
if toCheck.isPath {
if dbValue != "" {
// Ignore ENOENT on both, on a fresh system some paths
// may not exist this early in Libpod init.
dbVal, err := filepath.EvalSymlinks(dbValue)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("evaluating symlinks on DB %s path %q: %w", toCheck.name, dbValue, err)
}
dbValue = dbVal
}
if ourValue != "" {
ourVal, err := filepath.EvalSymlinks(ourValue)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("evaluating symlinks on configured %s path %q: %w", toCheck.name, ourValue, err)
}
ourValue = ourVal
}
}
if ourValue != dbValue {
// If the runtime value is the empty string and default is not,
// check against default.
if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
return true, nil
}
// If the DB value is the empty string, check that the runtime
// value is the default.
if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue {
if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue {
return true, nil
}
return true, fmt.Errorf("database %s %q does not match our %s %q: %w",
toCheck.name, dbValue, toCheck.name, toCheck.runtimeValue, define.ErrDBBadConfig)
toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig)
}
return true, nil

View File

@ -7,6 +7,7 @@ import (
"database/sql"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
goruntime "runtime"
@ -315,7 +316,7 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
);`
var (
os, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string
dbOS, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string
runtimeOS = goruntime.GOOS
runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir)
runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir)
@ -359,7 +360,7 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
row := tx.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;")
if err := row.Scan(&os, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil {
if err := row.Scan(&dbOS, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil {
if errors.Is(err, sql.ErrNoRows) {
if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS,
runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot,
@ -377,7 +378,26 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
return fmt.Errorf("retrieving DB config: %w", err)
}
checkField := func(fieldName, dbVal, ourVal string) error {
checkField := func(fieldName, dbVal, ourVal string, isPath bool) error {
if isPath {
// Evaluate symlinks. Ignore ENOENT. No guarantee all
// directories exist this early in Libpod init.
if dbVal != "" {
dbValClean, err := filepath.EvalSymlinks(dbVal)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("cannot evaluate symlinks on DB %s path %q: %w", fieldName, dbVal, err)
}
dbVal = dbValClean
}
if ourVal != "" {
ourValClean, err := filepath.EvalSymlinks(ourVal)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("cannot evaluate symlinks on our %s path %q: %w", fieldName, ourVal, err)
}
ourVal = ourValClean
}
}
if dbVal != ourVal {
return fmt.Errorf("database %s %q does not match our %s %q: %w", fieldName, dbVal, fieldName, ourVal, define.ErrDBBadConfig)
}
@ -385,25 +405,25 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
return nil
}
if err := checkField("os", os, runtimeOS); err != nil {
if err := checkField("os", dbOS, runtimeOS, false); err != nil {
return err
}
if err := checkField("static dir", staticDir, runtimeStaticDir); err != nil {
if err := checkField("static dir", staticDir, runtimeStaticDir, true); err != nil {
return err
}
if err := checkField("tmp dir", tmpDir, runtimeTmpDir); err != nil {
if err := checkField("tmp dir", tmpDir, runtimeTmpDir, true); err != nil {
return err
}
if err := checkField("graph root", graphRoot, runtimeGraphRoot); err != nil {
if err := checkField("graph root", graphRoot, runtimeGraphRoot, true); err != nil {
return err
}
if err := checkField("run root", runRoot, runtimeRunRoot); err != nil {
if err := checkField("run root", runRoot, runtimeRunRoot, true); err != nil {
return err
}
if err := checkField("graph driver", graphDriver, runtimeGraphDriver); err != nil {
if err := checkField("graph driver", graphDriver, runtimeGraphDriver, false); err != nil {
return err
}
if err := checkField("volume path", volumePath, runtimeVolumePath); err != nil {
if err := checkField("volume path", volumePath, runtimeVolumePath, true); err != nil {
return err
}

View File

@ -187,6 +187,21 @@ host.slirp4netns.executable | $expr_path
fi
}
@test "rootless podman with symlinked $HOME" {
# This is only needed as rootless, but we don't have a skip_if_root
# And it will not hurt to run as root.
skip_if_remote "path validation is only done in libpod, does not effect remote"
new_home=$PODMAN_TMPDIR/home
ln -s /home $new_home
# Just need the command to run cleanly
HOME=$PODMAN_TMPDIR/$HOME run_podman info
rm $new_home
}
@test "podman --root PATH --volumepath info - basic output" {
volumePath=${PODMAN_TMPDIR}/volumesGoHere
if ! is_remote; then