bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
// The digest cache exists for one reason — a pass that hashes every executable
|
||||
// on the box costs 2.9 s on this machine and the delta runs every minute — and
|
||||
// it is allowed to exist only if it cannot be talked into a stale answer. These
|
||||
// tests are that condition, not the speed.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// THE ATTACK THE KEY IS SHAPED AROUND. An in-place overwrite that restores the
|
||||
// old size and the old modification time defeats every field a cache normally
|
||||
// keys on. It does not defeat ctime, which the kernel moves on any write and no
|
||||
// syscall sets — and that is the entire argument for caching a digest in a
|
||||
// program whose job is to notice when a binary changes.
|
||||
func TestABinaryRewrittenWithItsOldTimestampIsNotServedFromTheCache(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Windows has no change time through Go's FileInfo; deltaCacheWeakTTL " +
|
||||
"covers it there, and deltacache_windows.go says so")
|
||||
}
|
||||
p := filepath.Join(t.TempDir(), "victim")
|
||||
if err := os.WriteFile(p, []byte("AAAAAAAA"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fi, err := os.Stat(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := &digestCache{}
|
||||
before, _ := c.digest(p)
|
||||
if before == "" {
|
||||
t.Fatal("the fixture was not hashed at all")
|
||||
}
|
||||
// Same length, same modification time, different content.
|
||||
if err := os.WriteFile(p, []byte("BBBBBBBB"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chtimes(p, fi.ModTime(), fi.ModTime()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, _ := c.digest(p)
|
||||
if after == before {
|
||||
t.Fatalf("the cache returned the OLD digest for a file whose contents had been "+
|
||||
"replaced under its original size and timestamp (%s). A digest cache that "+
|
||||
"can be defeated by `touch -r` hides exactly the event this plane exists "+
|
||||
"to report.", before[:12])
|
||||
}
|
||||
}
|
||||
|
||||
// And it does cache — otherwise the property above is held by a cache that
|
||||
// never hits, which is a different program.
|
||||
func TestTheCacheHitsWhenNothingMoved(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "steady")
|
||||
if err := os.WriteFile(p, []byte("hello"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := &digestCache{}
|
||||
a, _ := c.digest(p)
|
||||
b, _ := c.digest(p)
|
||||
if a != b || a == "" {
|
||||
t.Fatalf("two reads of one unchanged file gave %q and %q", a, b)
|
||||
}
|
||||
hits, misses, _ := c.stats()
|
||||
if hits != 1 || misses != 1 {
|
||||
t.Errorf("hits=%d misses=%d, want 1 and 1 — the second read went the long way "+
|
||||
"round, so the 60-second cadence costs a full hash of the box every time", hits, misses)
|
||||
}
|
||||
}
|
||||
|
||||
// A FAILURE IS A FACT ABOUT THE MOMENT, NOT ABOUT THE FILE. Caching "could not
|
||||
// be read" would mean a process whose executable becomes readable — the bastion
|
||||
// gaining rights, a mount appearing — is told for ever that it could not be
|
||||
// looked at.
|
||||
func TestAFileThatBecomesReadableIsDigestedRatherThanRemembered(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "later")
|
||||
c := &digestCache{}
|
||||
if d, note := c.digest(p); d != "" || note == "" {
|
||||
t.Fatalf("a path with no file gave digest %q note %q", d, note)
|
||||
}
|
||||
if err := os.WriteFile(p, []byte("now it exists"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d, _ := c.digest(p); d == "" {
|
||||
t.Fatal("the file exists now and the cache still says it cannot be read")
|
||||
}
|
||||
}
|
||||
|
||||
// The cache answers the same thing the uncached path does. It is a speed
|
||||
// device; the moment it is also a second opinion, the accounting and the delta
|
||||
// can disagree about what is running.
|
||||
func TestTheCachedAnswerIsTheUncachedAnswer(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "same")
|
||||
if err := os.WriteFile(p, []byte("content"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, wantNote := baselineExeDigest(p)
|
||||
c := &digestCache{}
|
||||
got, gotNote := c.digest(p)
|
||||
if got != want || gotNote != wantNote {
|
||||
t.Fatalf("cached (%q,%q) != direct (%q,%q)", got, gotNote, want, wantNote)
|
||||
}
|
||||
// Including the refusals: a directory is not an executable either way.
|
||||
d1, n1 := baselineExeDigest(filepath.Dir(p))
|
||||
d2, n2 := c.digest(filepath.Dir(p))
|
||||
if d1 != d2 || n1 != n2 {
|
||||
t.Fatalf("cached (%q,%q) != direct (%q,%q) for a directory", d2, n2, d1, n1)
|
||||
}
|
||||
}
|
||||
|
||||
// A weakly-keyed entry is re-hashed on a timer. Held here as a fact about the
|
||||
// constant rather than by waiting fifteen minutes: if someone lengthens it to
|
||||
// hours, the Windows hole widens with no test saying so.
|
||||
func TestTheWeakKeyTTLStaysShortEnoughToMatter(t *testing.T) {
|
||||
if deltaCacheWeakTTL > 30*time.Minute {
|
||||
t.Fatalf("deltaCacheWeakTTL is %v — on a platform with no change time this is "+
|
||||
"how long a size-and-timestamp-preserving overwrite reads as known",
|
||||
deltaCacheWeakTTL)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user