mirror of
https://github.com/gotify/server.git
synced 2026-09-06 15:57:32 +08:00
`Manager.RemoveUser` deletes from `m.instances` without holding `m.mutex`:
delete(m.instances, pluginConf.ID)
Every other access to `m.instances` is synchronized — `Instance` reads it
under `m.mutex.RLock()`, and the writes in `InitializeForUserID` /
`initializeSingleUserPlugin` happen under `m.mutex.Lock()`. The adjacent
`inst.Disable()` call in this same loop is even wrapped in Lock/Unlock, so
only the map delete is left unguarded.
`RemoveUser` is registered as the `OnUserDeleted` callback (router.go), fired
when an admin deletes a user, while any authenticated request to the plugin
API (`GET /plugin`, `/plugin/:id/*`) concurrently reads `m.instances` via
`Instance`. A `RLock` reader is not protected against a writer that never
takes the mutex, so this races and triggers Go's runtime-fatal
"concurrent map read and map write", crashing the whole server (a fatal that
gin.Recovery cannot recover). Take the write lock around the delete, matching
every other access.