gofiber / contrib

🧬 Repository for third party middlewares with dependencies
https://docs.gofiber.io/contrib/
MIT License
198 stars 102 forks source link

🐛 [Bug]: Segfault with WS compression #1136

Open nuttert opened 2 weeks ago

nuttert commented 2 weeks ago

Bug Description

I got the segfault, when I turned on a compression:

    c.EnableWriteCompression(true)
    c.SetCompressionLevel(flate.BestSpeed + 1)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x48 pc=0x6f56a3]
goroutine 291238 [running]:
github.com/valyala/fasthttp.(*hijackConn).SetWriteDeadline(0x0?, {0x0?, 0x0?, 0x0?})
        <autogenerated>:1 +0x23
github.com/fasthttp/websocket.(*Conn).write(0xc008e04420, 0x0, {0x0?, 0x0?, 0x0?}, {0xc010b07b0a, 0x404, 0x404}, {0x0, 0x0, ...})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/conn.go:425 +0x176
github.com/fasthttp/websocket.(*messageWriter).flushFrame(0xc00e7144e0, 0x0, {0x0?, 0x0?, 0x0?})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/conn.go:676 +0x3c6
github.com/fasthttp/websocket.(*messageWriter).ncopy(0xc00e7144e0, 0xee)
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/conn.go:701 +0x36
github.com/fasthttp/websocket.(*messageWriter).Write(0xc00e7144e0, {0xc0000f6068?, 0xf2, 0x694538?})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/conn.go:728 +0xc8
github.com/fasthttp/websocket.(*truncWriter).Write(0xc013272400, {0xc0000f6068?, 0x3e000000000000?, 0x6?})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/compression.go:92 +0x216
github.com/klauspost/compress/flate.(*huffmanBitWriter).writeTokens(0xc0000f6008, {0xc00576a314, 0xc005d93cef?, 0x310?}, {0xc00031a000, 0x2?, 0x17ceb0e7144e0?}, {0xc00ce24080, 0x1e, 0x20})
        /go/pkg/mod/github.com/klauspost/compress@v1.17.9/flate/huffman_bit_writer.go:966 +0x68d
github.com/klauspost/compress/flate.(*huffmanBitWriter).writeBlockDynamic(0xc0000f6008, 0xc00576a088, 0x0, {0xc003e00000, 0xffff, 0xffff}, 0x1?)
        /go/pkg/mod/github.com/klauspost/compress@v1.17.9/flate/huffman_bit_writer.go:767 +0x894
github.com/klauspost/compress/flate.(*compressor).storeFast(0xc00576a000)
        /go/pkg/mod/github.com/klauspost/compress@v1.17.9/flate/deflate.go:767 +0x278
github.com/klauspost/compress/flate.(*compressor).write(0xc00576a000, {0xc015f4e000?, 0x249aa9, 0xc00e7144e0?})
        /go/pkg/mod/github.com/klauspost/compress@v1.17.9/flate/deflate.go:783 +0x88
github.com/klauspost/compress/flate.(*Writer).Write(...)
        /go/pkg/mod/github.com/klauspost/compress@v1.17.9/flate/deflate.go:971
github.com/fasthttp/websocket.(*flateWriteWrapper).Write(0xc008e04420?, {0xc015f4e000?, 0x7f7758288f18?, 0x10?})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/compression.go:106 +0x25
github.com/fasthttp/websocket.(*Conn).WriteMessage(0xc00a104220?, 0x8c59d5?, {0xc015f4e000, 0x249aa9, 0x2a0000})
        /go/pkg/mod/github.com/fasthttp/websocket@v1.5.7/conn.go:837 +0x186
microservices/routers/ws/impl.(*Router).writeMessage(0x8124a0?, 0xc001d50200, {0xc015f4e000, 0x249aa9, 0x2a0000}, {0xc00a104220, 0xf})

How to Reproduce

Steps to reproduce the behavior: Turn on the compression and after several hours(if you actively use the server) you will get a segfault

Expected Behavior

I expect that compression should work fine without segfaults

Contrib package Version

contrib/websocket v1.3.0

Code Snippet (optional)

c.EnableWriteCompression(true)
c.SetCompressionLevel(flate.BestSpeed + 1)

Checklist:

gaby commented 2 weeks ago

@nuttert Can you post a full example ?

nuttert commented 2 weeks ago

Handler:

import "github.com/gofiber/contrib/websocket"

type WSConnection = websocket.Conn

func (r *Router) WSHandler(c *WSConnection) {
    id := strings.Clone(c.RemoteAddr().String())
    c.EnableWriteCompression(true)
    c.SetCompressionLevel(flate.BestSpeed + 1)

    defer c.Close()

    r.connections.Set(id, c)
    // Publisher spawns a separate goroutine that receives messages from a channel,
    // then on each message in the goroutine the callback is called
    pub := r.setupPublisher(id, func(headerBytes, msg []byte) bool {
        return r.writeMessage(c, msg, id)
    })
    ...
    // Read from the connection in the goroutine
}

Write message to the gofiber connection:

func (r *Router) writeMessage(c *WSConnection, msg []byte, id string) bool {
    if c == nil {
        log.Error().Msgf("Connection is not initialized %s", id)
        return false
    }
    if len(msg) == 0 {
        return false
    }

    if err := c.WriteMessage(websocket.TextMessage, msg); err != nil {
        return false
    }
    return true
}

Listener:

        app := fiber.New()
    app.Use("/ws", func(c *fiber.Ctx) error {
        if websocket.IsWebSocketUpgrade(c) {
            c.Locals("allowed", true)
            return c.Next()
        }
        return fiber.ErrUpgradeRequired
    })

    app.Get(*args.RouterWSPath, websocket.New(r.WSHandler, websocket.Config{
        EnableCompression: true,
    }))