From b276e7ef21c62cdd491d3a7e0cbda156c1e05a47 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 27 May 2025 14:08:39 -0400 Subject: [PATCH] Fix SQLite volume lookup queries matching too liberally Specifically, this does two things: 1. Turn on case-sensitive LIKE queries. Technically, this is not specific to volumes, as it will also affect container and pod lookups - but there, it only affects IDs. So `podman rm abc123` will not be the same as `podman rm ABC123` but I don't think anyone was manually entering uppercase SHA256 hash IDs so it shouldn't matter. 2. Escape the _ and % characters in volume lookup queries. These are SQLite wildcards, and meant that `podman volume rm test_1` would also match `podman volume rm testa2` (or any character in place of the underscore). This isn't done with pod and container lookups, but again those just use LIKE for IDs - so technically `podman volume rm abc_123` probably works and removes containers with an ID matching that pattern... I don't think that matters though. Fixes #26168 Signed-off-by: Matthew Heon --- libpod/sqlite_state.go | 9 +++++++-- test/e2e/volume_rm_test.go | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libpod/sqlite_state.go b/libpod/sqlite_state.go index ea42f8e3e8..72fb83f62f 100644 --- a/libpod/sqlite_state.go +++ b/libpod/sqlite_state.go @@ -39,13 +39,16 @@ const ( sqliteOptionForeignKeys = "&_foreign_keys=1" // Make sure that transactions happen exclusively. sqliteOptionTXLock = "&_txlock=exclusive" + // Enforce case sensitivity for LIKE + sqliteOptionCaseSensitiveLike = "&_cslike=TRUE" // Assembled sqlite options used when opening the database. sqliteOptions = "db.sql?" + sqliteOptionLocation + sqliteOptionSynchronous + sqliteOptionForeignKeys + - sqliteOptionTXLock + sqliteOptionTXLock + + sqliteOptionCaseSensitiveLike ) // NewSqliteState creates a new SQLite-backed state database. @@ -2210,7 +2213,9 @@ func (s *SQLiteState) LookupVolume(name string) (*Volume, error) { return nil, define.ErrDBClosed } - rows, err := s.conn.Query("SELECT Name, JSON FROM VolumeConfig WHERE Name LIKE ? ORDER BY LENGTH(Name) ASC;", name+"%") + escaper := strings.NewReplacer("\\", "\\\\", "_", "\\_", "%", "\\%") + queryString := escaper.Replace(name) + "%" + rows, err := s.conn.Query("SELECT Name, JSON FROM VolumeConfig WHERE Name LIKE ? ESCAPE '\\' ORDER BY LENGTH(Name) ASC;", queryString) if err != nil { return nil, fmt.Errorf("querying database for volume %s: %w", name, err) } diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 7333ce1a47..28d45dc37a 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -114,4 +114,14 @@ var _ = Describe("Podman volume rm", func() { Expect(session).Should(ExitCleanly()) Expect(len(session.OutputToStringArray())).To(BeNumerically(">=", 2)) }) + + It("podman volume rm by unique partial name - case & underscore insensitive", func() { + volNames := []string{"test_volume", "test-volume", "test", "Test"} + for _, name := range volNames { + podmanTest.PodmanExitCleanly("volume", "create", name) + } + + podmanTest.PodmanExitCleanly("volume", "rm", volNames[0]) + podmanTest.PodmanExitCleanly("volume", "rm", volNames[2]) + }) })