From 1735f1c53ce9e48b4814086001b9f2e9cc0ceb4e Mon Sep 17 00:00:00 2001 From: makeworld Date: Wed, 9 Dec 2020 15:17:07 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20concurrent=20map=20pan?= =?UTF-8?q?ic=20for=20tofuStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/tofu.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/client/tofu.go b/client/tofu.go index 7af273e..479be52 100644 --- a/client/tofu.go +++ b/client/tofu.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "strings" + "sync" "time" "github.com/makeworld-the-better-one/amfora/config" @@ -21,6 +22,12 @@ var ErrTofu = errors.New("server cert does not match TOFU database") var tofuStore = config.TofuStore +// tofuStoreMu protects tofuStore, since viper is not thread-safe. +// See this issue for details: https://github.com/spf13/viper/issues/268 +// This is needed because Gemini requests may happen concurrently and +// call on the funcs on this file. +var tofuStoreMu = sync.RWMutex{} + // idKey returns the config/viper key needed to retrieve // a cert's ID / fingerprint. func idKey(domain string, port string) string { @@ -38,6 +45,9 @@ func expiryKey(domain string, port string) string { } func loadTofuEntry(domain string, port string) (string, time.Time, error) { + tofuStoreMu.RLock() + defer tofuStoreMu.RUnlock() + id := tofuStore.GetString(idKey(domain, port)) // Fingerprint if len(id) != sha256.Size*2 { // Not set, or invalid @@ -68,6 +78,9 @@ func origCertID(cert *x509.Certificate) string { } func saveTofuEntry(domain, port string, cert *x509.Certificate) { + tofuStoreMu.Lock() + defer tofuStoreMu.Unlock() + tofuStore.Set(idKey(domain, port), certID(cert)) tofuStore.Set(expiryKey(domain, port), cert.NotAfter.UTC()) tofuStore.WriteConfig() //nolint:errcheck // Not an issue if it's not saved, only cached data @@ -90,9 +103,10 @@ func handleTofu(domain, port string, cert *x509.Certificate) bool { // Same cert as the one stored // Store expiry again in case it changed + tofuStoreMu.Lock() tofuStore.Set(expiryKey(domain, port), cert.NotAfter.UTC()) tofuStore.WriteConfig() //nolint:errcheck - + tofuStoreMu.Unlock() return true } if origCertID(cert) == id { @@ -117,5 +131,8 @@ func ResetTofuEntry(domain, port string, cert *x509.Certificate) { // GetExpiry returns the stored expiry date for the given host. // The time will be empty (zero) if there is not expiry date stored for that host. func GetExpiry(domain, port string) time.Time { + tofuStoreMu.RLock() + defer tofuStoreMu.RUnlock() + return tofuStore.GetTime(expiryKey(domain, port)) }