-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Hello.
There seems to be a mistake in *baseClient.initConn().
The following code panics:
package main
import (
"context"
"fmt"
"os"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
rdb.Options().MaintNotificationsConfig = nil
resp := rdb.Publish(context.Background(), "test", "test")
result, err := resp.Result()
if err != nil {
rdb.Close()
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(result)
}*client.Options() clearly states that the returned value should be treated as read-only and the above panic seems to be impossible to trigger through normal means without modifying *redis.Options.
However, if we take a look at the code of *baseClient.initConn(), we can see that, on line 538, the method expects a nil MaintNotificationsConfig. But there is no such check on line 540, where the EndpointType field is accessed, causing the above panic.
Considering the check right before that, this does not appear to be intentional behavior.
This panic also leaves the read lock on optLock mutex, and the following example deadlocks:
package main
import (
"context"
"fmt"
"os"
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/v9/maintnotifications"
)
func main() {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
rdb.Options().MaintNotificationsConfig = nil
defer func() {
if r := recover(); r != nil {
rdb.Options().MaintNotificationsConfig = maintnotifications.DefaultConfig()
_ = rdb.Publish(context.Background(), "test", "test")
}
}()
resp := rdb.Publish(context.Background(), "test", "test")
result, err := resp.Result()
if err != nil {
rdb.Close()
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(result)
}Expected Behavior
0
or whatever the actual number of subscribers there was.
Current Behavior
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x5f9b91]
goroutine 1 [running]:
github.com/redis/go-redis/v9.(*baseClient).initConn(0xc0000000e0, {0x785860, 0x980840}, 0xc000112000)
github.com/redis/go-redis/v9@v9.17.2/redis.go:540 +0xd51
github.com/redis/go-redis/v9.(*baseClient)._getConn(0xc0000000e0, {0x785860, 0x980840})
github.com/redis/go-redis/v9@v9.17.2/redis.go:293 +0x74
github.com/redis/go-redis/v9.(*baseClient).getConn(0xc0000000e0, {0x785860?, 0x980840?})
github.com/redis/go-redis/v9@v9.17.2/redis.go:272 +0x65
github.com/redis/go-redis/v9.(*baseClient).withConn(0xc0000000e0, {0x785860, 0x980840}, 0xc0000f3d70)
github.com/redis/go-redis/v9@v9.17.2/redis.go:645 +0x49
github.com/redis/go-redis/v9.(*baseClient)._process(0x5fb94d?, {0x785860?, 0x980840?}, {0x787618?, 0xc00007c240?}, 0xc0000c0000?)
github.com/redis/go-redis/v9@v9.17.2/redis.go:700 +0xe7
github.com/redis/go-redis/v9.(*baseClient).process(0xc0000000e0, {0x785860, 0x980840}, {0x787618, 0xc00007c240})
github.com/redis/go-redis/v9@v9.17.2/redis.go:669 +0x76
github.com/redis/go-redis/v9.(*hooksMixin).processHook(...)
github.com/redis/go-redis/v9@v9.17.2/redis.go:200
github.com/redis/go-redis/v9.(*Client).Process(0xc000065e90?, {0x785860?, 0x980840?}, {0x787618, 0xc00007c240})
github.com/redis/go-redis/v9@v9.17.2/redis.go:1120 +0x3e
github.com/redis/go-redis/v9.cmdable.Publish(...)
github.com/redis/go-redis/v9@v9.17.2/pubsub_commands.go:18
main.main()
example.com/redis-bug/main.go:17 +0x153
exit status 2
or a deadlock in the second example.
Possible Solution
Probably something like:
diff --git a/redis.go b/redis.go
index a6a71067..9cdf988d 100644
--- a/redis.go
+++ b/redis.go
@@ -535,9 +535,13 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
// Enable maintnotifications if maintnotifications are configured
c.optLock.RLock()
- maintNotifEnabled := c.opt.MaintNotificationsConfig != nil && c.opt.MaintNotificationsConfig.Mode != maintnotifications.ModeDisabled
+ maintNotifEnabled := false
protocol := c.opt.Protocol
- endpointType := c.opt.MaintNotificationsConfig.EndpointType
+ var endpointType maintnotifications.EndpointType
+ if c.opt.MaintNotificationsConfig != nil {
+ maintNotifEnabled = c.opt.MaintNotificationsConfig.Mode != maintnotifications.ModeDisabled
+ endpointType = c.opt.MaintNotificationsConfig.EndpointType
+ }
c.optLock.RUnlock()
var maintNotifHandshakeErr error
if maintNotifEnabled && protocol == 3 {Context
I don't think this is a big problem or anything serious since the observed behavior can currently only be achieved by modifying a read-only object.
But if the library's behavior changes in the future, this could cause a real problem.
Environment
Fedora Linux 43
amd64
Redis 8.4.0
go-redis v9.17.2
go1.25.5