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 package libpod
import ( import (
"errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -93,6 +95,7 @@ type dbConfigValidation struct {
runtimeValue string runtimeValue string
key []byte key []byte
defaultValue string defaultValue string
isPath bool
} }
// Check if the configuration of the database is compatible with the // 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, runtime.GOOS,
osKey, osKey,
runtime.GOOS, runtime.GOOS,
false,
}, },
{ {
"libpod root directory (staticdir)", "libpod root directory (staticdir)",
filepath.Clean(rt.config.Engine.StaticDir), filepath.Clean(rt.config.Engine.StaticDir),
staticDirKey, staticDirKey,
"", "",
true,
}, },
{ {
"libpod temporary files directory (tmpdir)", "libpod temporary files directory (tmpdir)",
filepath.Clean(rt.config.Engine.TmpDir), filepath.Clean(rt.config.Engine.TmpDir),
tmpDirKey, tmpDirKey,
"", "",
true,
}, },
{ {
"storage temporary directory (runroot)", "storage temporary directory (runroot)",
filepath.Clean(rt.StorageConfig().RunRoot), filepath.Clean(rt.StorageConfig().RunRoot),
runRootKey, runRootKey,
storeOpts.RunRoot, storeOpts.RunRoot,
true,
}, },
{ {
"storage graph root directory (graphroot)", "storage graph root directory (graphroot)",
filepath.Clean(rt.StorageConfig().GraphRoot), filepath.Clean(rt.StorageConfig().GraphRoot),
graphRootKey, graphRootKey,
storeOpts.GraphRoot, storeOpts.GraphRoot,
true,
}, },
{ {
"storage graph driver", "storage graph driver",
rt.StorageConfig().GraphDriverName, rt.StorageConfig().GraphDriverName,
graphDriverKey, graphDriverKey,
storeOpts.GraphDriverName, storeOpts.GraphDriverName,
false,
}, },
{ {
"volume path", "volume path",
rt.config.Engine.VolumePath, rt.config.Engine.VolumePath,
volPathKey, volPathKey,
"", "",
true,
}, },
} }
@ -221,22 +231,45 @@ func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bo
} }
dbValue := string(keyBytes) 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, // If the runtime value is the empty string and default is not,
// check against default. // check against default.
if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
return true, nil return true, nil
} }
// If the DB value is the empty string, check that the runtime // If the DB value is the empty string, check that the runtime
// value is the default. // value is the default.
if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue { if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue {
return true, nil return true, nil
} }
return true, fmt.Errorf("database %s %q does not match our %s %q: %w", 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 return true, nil

View File

@ -7,6 +7,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
goruntime "runtime" goruntime "runtime"
@ -315,14 +316,14 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
);` );`
var ( var (
os, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string dbOS, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string
runtimeOS = goruntime.GOOS runtimeOS = goruntime.GOOS
runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir) runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir)
runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir) runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir)
runtimeGraphRoot = filepath.Clean(s.runtime.StorageConfig().GraphRoot) runtimeGraphRoot = filepath.Clean(s.runtime.StorageConfig().GraphRoot)
runtimeRunRoot = filepath.Clean(s.runtime.StorageConfig().RunRoot) runtimeRunRoot = filepath.Clean(s.runtime.StorageConfig().RunRoot)
runtimeGraphDriver = s.runtime.StorageConfig().GraphDriverName runtimeGraphDriver = s.runtime.StorageConfig().GraphDriverName
runtimeVolumePath = filepath.Clean(s.runtime.config.Engine.VolumePath) runtimeVolumePath = filepath.Clean(s.runtime.config.Engine.VolumePath)
) )
// Some fields may be empty, indicating they are set to the default. // Some fields may be empty, indicating they are set to the default.
@ -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;") 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 errors.Is(err, sql.ErrNoRows) {
if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS, if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS,
runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot, runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot,
@ -377,7 +378,26 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
return fmt.Errorf("retrieving DB config: %w", err) 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 { if dbVal != ourVal {
return fmt.Errorf("database %s %q does not match our %s %q: %w", fieldName, dbVal, fieldName, ourVal, define.ErrDBBadConfig) 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 return nil
} }
if err := checkField("os", os, runtimeOS); err != nil { if err := checkField("os", dbOS, runtimeOS, false); err != nil {
return err return err
} }
if err := checkField("static dir", staticDir, runtimeStaticDir); err != nil { if err := checkField("static dir", staticDir, runtimeStaticDir, true); err != nil {
return err return err
} }
if err := checkField("tmp dir", tmpDir, runtimeTmpDir); err != nil { if err := checkField("tmp dir", tmpDir, runtimeTmpDir, true); err != nil {
return err return err
} }
if err := checkField("graph root", graphRoot, runtimeGraphRoot); err != nil { if err := checkField("graph root", graphRoot, runtimeGraphRoot, true); err != nil {
return err return err
} }
if err := checkField("run root", runRoot, runtimeRunRoot); err != nil { if err := checkField("run root", runRoot, runtimeRunRoot, true); err != nil {
return err return err
} }
if err := checkField("graph driver", graphDriver, runtimeGraphDriver); err != nil { if err := checkField("graph driver", graphDriver, runtimeGraphDriver, false); err != nil {
return err return err
} }
if err := checkField("volume path", volumePath, runtimeVolumePath); err != nil { if err := checkField("volume path", volumePath, runtimeVolumePath, true); err != nil {
return err return err
} }

View File

@ -187,6 +187,21 @@ host.slirp4netns.executable | $expr_path
fi 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" { @test "podman --root PATH --volumepath info - basic output" {
volumePath=${PODMAN_TMPDIR}/volumesGoHere volumePath=${PODMAN_TMPDIR}/volumesGoHere
if ! is_remote; then if ! is_remote; then