mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 18:24:35 +03:00
fix data race in code for logging login attempts
logging of login attempts happens in the background, because we don't want to block regular operation with disk since for such logging. however, when a line is logged, we evaluate some attributes of a connection, notably the username. but about when we do authentication, we change the username on a connection. so we were reading and writing at the same time. this is now fixed by evaluating the attributes before we pass off the logger to the goroutine. found by the go race detector.
This commit is contained in:
@ -21,7 +21,7 @@ import (
|
||||
var AuthDB *bstore.DB
|
||||
var AuthDBTypes = []any{TLSPublicKey{}, LoginAttempt{}, LoginAttemptState{}}
|
||||
|
||||
// Init opens auth.db.
|
||||
// Init opens auth.db and starts the login writer.
|
||||
func Init(ctx context.Context) error {
|
||||
if AuthDB != nil {
|
||||
return fmt.Errorf("already initialized")
|
||||
@ -36,7 +36,7 @@ func Init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
startLoginAttemptWriter(ctx)
|
||||
startLoginAttemptWriter()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
@ -67,12 +67,18 @@ func Init(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes auth.db.
|
||||
// Close closes auth.db and stops the login writer.
|
||||
func Close() error {
|
||||
if AuthDB == nil {
|
||||
return fmt.Errorf("not open")
|
||||
}
|
||||
|
||||
stopc := make(chan struct{})
|
||||
writeLoginAttemptStop <- stopc
|
||||
<-stopc
|
||||
|
||||
err := AuthDB.Close()
|
||||
AuthDB = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/mjl-/mox/metrics"
|
||||
"github.com/mjl-/mox/mlog"
|
||||
"github.com/mjl-/mox/mox-"
|
||||
)
|
||||
|
||||
var loginAttemptsMaxPerAccount = 10 * 1000 // Lower during tests.
|
||||
@ -101,16 +100,35 @@ const (
|
||||
)
|
||||
|
||||
var writeLoginAttempt chan LoginAttempt
|
||||
var writeLoginAttemptStopped chan struct{} // For synchronizing with tests.
|
||||
var writeLoginAttemptStop chan chan struct{}
|
||||
|
||||
func startLoginAttemptWriter(ctx context.Context) {
|
||||
func startLoginAttemptWriter() {
|
||||
writeLoginAttempt = make(chan LoginAttempt, 100)
|
||||
writeLoginAttemptStopped = make(chan struct{}, 1)
|
||||
writeLoginAttemptStop = make(chan chan struct{})
|
||||
|
||||
process := func(la *LoginAttempt) {
|
||||
var l []LoginAttempt
|
||||
if la != nil {
|
||||
l = []LoginAttempt{*la}
|
||||
}
|
||||
// Gather all that we can write now.
|
||||
All:
|
||||
for {
|
||||
select {
|
||||
case xla := <-writeLoginAttempt:
|
||||
l = append(l, xla)
|
||||
default:
|
||||
break All
|
||||
}
|
||||
}
|
||||
|
||||
if len(l) > 0 {
|
||||
loginAttemptWrite(l...)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
writeLoginAttemptStopped <- struct{}{}
|
||||
|
||||
x := recover()
|
||||
if x == nil {
|
||||
return
|
||||
@ -121,26 +139,15 @@ func startLoginAttemptWriter(ctx context.Context) {
|
||||
metrics.PanicInc(metrics.Store)
|
||||
}()
|
||||
|
||||
done := ctx.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
case stopc := <-writeLoginAttemptStop:
|
||||
process(nil)
|
||||
stopc <- struct{}{}
|
||||
return
|
||||
|
||||
case la := <-writeLoginAttempt:
|
||||
l := []LoginAttempt{la}
|
||||
// Gather all that we can write now.
|
||||
All:
|
||||
for {
|
||||
select {
|
||||
case la = <-writeLoginAttempt:
|
||||
l = append(l, la)
|
||||
default:
|
||||
break All
|
||||
}
|
||||
}
|
||||
|
||||
loginAttemptWrite(l...)
|
||||
process(&la)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@ -157,14 +164,8 @@ func LoginAttemptAdd(ctx context.Context, log mlog.Log, a LoginAttempt) {
|
||||
metrics.AuthenticationInc(a.Protocol, a.AuthMech, string(a.Result))
|
||||
|
||||
a.log = log
|
||||
select {
|
||||
case <-mox.Context.Done():
|
||||
// During shutdown, don't return before writing.
|
||||
loginAttemptWrite(a)
|
||||
default:
|
||||
// Send login attempt to writer. Only blocks if there are lots of login attempts.
|
||||
writeLoginAttempt <- a
|
||||
}
|
||||
// Send login attempt to writer. Only blocks if there are lots of login attempts.
|
||||
writeLoginAttempt <- a
|
||||
}
|
||||
|
||||
func loginAttemptWrite(l ...LoginAttempt) {
|
||||
|
@ -17,12 +17,19 @@ func TestLoginAttempt(t *testing.T) {
|
||||
mox.MustLoadConfig(true, false)
|
||||
|
||||
xctx, xcancel := context.WithCancel(ctxbg)
|
||||
defer xcancel() // Stop clearing of LoginAttempts.
|
||||
err := Init(xctx)
|
||||
tcheck(t, err, "store init")
|
||||
// Stop the background LoginAttempt writer for synchronous tests.
|
||||
xcancel()
|
||||
<-writeLoginAttemptStopped
|
||||
stopc := make(chan struct{})
|
||||
writeLoginAttemptStop <- stopc
|
||||
<-stopc
|
||||
defer func() {
|
||||
// Ensure Close() below finishes
|
||||
go func() {
|
||||
c := <-writeLoginAttemptStop
|
||||
c <- struct{}{}
|
||||
}()
|
||||
|
||||
err := Close()
|
||||
tcheck(t, err, "store close")
|
||||
}()
|
||||
|
Reference in New Issue
Block a user