diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 4ee967f..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Code scanning - action" - -on: - push: - pull_request: - schedule: - - cron: '0 19 * * 0' - -jobs: - CodeQL-Build: - - # CodeQL runs on ubuntu-latest and windows-latest - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 37d5147..65c5fac 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [1.22.x] + go-version: [1.24.0] steps: - name: Set up Go ${{ matrix.go-version }} uses: actions/setup-go@v2 @@ -41,7 +41,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go-version: [1.21.x, 1.22.x] + go-version: 1.24.0 os: [ubuntu-latest, windows-latest, macos-latest] steps: - name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }} diff --git a/go.mod b/go.mod index 02de0d0..c41dfc7 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/minio/sio +go 1.24 + require ( golang.org/x/crypto v0.23.0 golang.org/x/sys v0.20.0 ) require golang.org/x/term v0.20.0 - -go 1.18 diff --git a/reader-v1.go b/reader-v1.go index e20233d..be58f74 100644 --- a/reader-v1.go +++ b/reader-v1.go @@ -24,6 +24,7 @@ type encReaderV10 struct { src io.Reader buffer packageV10 + recycle func() // Returns the buffer to the pool offset int payloadSize int stateErr error @@ -36,19 +37,16 @@ func encryptReaderV10(src io.Reader, config *Config) (*encReaderV10, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &encReaderV10{ authEncV10: ae, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, payloadSize: config.PayloadSize, }, nil } -func (r *encReaderV10) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *encReaderV10) Read(p []byte) (int, error) { if r.stateErr != nil { return 0, r.stateErr @@ -99,6 +97,7 @@ type decReaderV10 struct { src io.Reader buffer packageV10 + recycle func() // Returns the buffer to the pool offset int stateErr error } @@ -110,18 +109,15 @@ func decryptReaderV10(src io.Reader, config *Config) (*decReaderV10, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decReaderV10{ authDecV10: ad, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } -func (r *decReaderV10) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *decReaderV10) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -226,12 +222,14 @@ func (r *decReaderAtV10) ReadAt(p []byte, offset int64) (n int, err error) { return 0, errUnexpectedSize } - buffer := packageBufferPool.Get().([]byte)[:maxPackageSize] - defer recyclePackageBufferPool(buffer) + buffer, recycle := getBuffer() + defer recycle() + decReader := decReaderV10{ authDecV10: r.ad, src: §ionReader{r.src, t * maxPackageSize}, buffer: packageV10(buffer), + recycle: recycle, offset: 0, } decReader.SeqNum = uint32(t) diff --git a/reader-v2.go b/reader-v2.go index b8794bc..ab06e90 100644 --- a/reader-v2.go +++ b/reader-v2.go @@ -25,6 +25,7 @@ type encReaderV20 struct { src io.Reader buffer packageV20 + recycle func() // Returns the buffer to the pool offset int lastByte byte stateErr error @@ -32,17 +33,19 @@ type encReaderV20 struct { firstRead bool } -var packageBufferPool = sync.Pool{New: func() interface{} { return make([]byte, maxPackageSize) }} +var packageBufferPool = sync.Pool{ + New: func() any { + b := make([]byte, maxPackageSize) + return &b + }, +} -func recyclePackageBufferPool(b []byte) { - if cap(b) < maxPackageSize { - return - } - // Clear so we don't potentially leak info between callers - for i := range b { - b[i] = 0 - } - packageBufferPool.Put(b) +func getBuffer() ([]byte, func()) { + p := packageBufferPool.Get().(*[]byte) + return *p, sync.OnceFunc(func() { + clear(*p) // Clear to avoid leaking data between callers + packageBufferPool.Put(p) + }) } // encryptReaderV20 returns an io.Reader wrapping the given io.Reader. @@ -52,19 +55,17 @@ func encryptReaderV20(src io.Reader, config *Config) (*encReaderV20, error) { if err != nil { return nil, err } + + buffer, recycle := getBuffer() return &encReaderV20{ authEncV20: ae, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, firstRead: true, }, nil } -func (r *encReaderV20) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *encReaderV20) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -146,6 +147,7 @@ type decReaderV20 struct { stateErr error buffer packageV20 + recycle func() offset int } @@ -156,18 +158,15 @@ func decryptReaderV20(src io.Reader, config *Config) (*decReaderV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decReaderV20{ authDecV20: ad, src: src, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } -func (r *decReaderV20) recycle() { - recyclePackageBufferPool(r.buffer) - r.buffer = nil -} - func (r *decReaderV20) Read(p []byte) (n int, err error) { if r.stateErr != nil { return 0, r.stateErr @@ -296,13 +295,16 @@ func (r *decReaderAtV20) ReadAt(p []byte, offset int64) (n int, err error) { t-- } + buffer, recycle := getBuffer() + defer recycle() + decReader := decReaderV20{ authDecV20: r.ad, src: §ionReader{r.src, t * maxPackageSize}, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, offset: 0, } - defer decReader.recycle() decReader.SeqNum = uint32(t) if k > 0 { if _, err := io.CopyN(io.Discard, &decReader, k); err != nil { @@ -312,7 +314,7 @@ func (r *decReaderAtV20) ReadAt(p []byte, offset int64) (n int, err error) { for n < len(p) && err == nil { var nn int - nn, err = (&decReader).Read(p[n:]) + nn, err = decReader.Read(p[n:]) n += nn } if err == io.EOF && n == len(p) { diff --git a/writer-v1.go b/writer-v1.go index 5aa247a..7488633 100644 --- a/writer-v1.go +++ b/writer-v1.go @@ -14,13 +14,16 @@ package sio -import "io" +import ( + "io" +) type decWriterV10 struct { authDecV10 dst io.Writer buffer packageV10 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -35,14 +38,13 @@ func decryptWriterV10(dst io.Writer, config *Config) (*decWriterV10, error) { if err != nil { return nil, err } - buf := packageBufferPool.Get().([]byte)[:maxPackageSize] - for i := range buf { - buf[i] = 0 - } + + buffer, recycle := getBuffer() return &decWriterV10{ authDecV10: ad, dst: dst, - buffer: buf, + buffer: buffer, + recycle: recycle, }, nil } @@ -67,9 +69,13 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { } n += copy(w.buffer[w.offset:], p[:remaining]) if err = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, w.buffer.Payload()); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] @@ -83,9 +89,13 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { return n, err } if err = w.Open(w.buffer[headerSize:packageLen-tagSize], p[:packageLen]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, w.buffer[headerSize:packageLen-tagSize]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[packageLen:] @@ -99,34 +109,39 @@ func (w *decWriterV10) Write(p []byte) (n int, err error) { } func (w *decWriterV10) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if dst, ok := w.dst.(io.Closer); ok { + dst.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() + if w.offset > 0 { if w.offset <= headerSize+tagSize { - return errInvalidPayloadSize // the payload is always > 0 + w.closeErr = errInvalidPayloadSize // the payload is always > 0 } - header := headerV10(w.buffer[:headerSize]) - if w.offset < headerSize+header.Len()+tagSize { - return errInvalidPayloadSize // there is less data than specified by the header - } - if err = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]); err != nil { - return err - } - if err = flush(w.dst, w.buffer.Payload()); err != nil { // write to underlying io.Writer - return err + if w.closeErr == nil { + header := headerV10(w.buffer[:headerSize]) + if w.offset < headerSize+header.Len()+tagSize { + w.closeErr = errInvalidPayloadSize + } + if w.closeErr == nil { + w.closeErr = w.Open(w.buffer.Payload(), w.buffer[:w.buffer.Length()]) + } + if w.closeErr == nil { + w.closeErr = flush(w.dst, w.buffer.Payload()) + } } } if dst, ok := w.dst.(io.Closer); ok { - return dst.Close() + if err := dst.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } type encWriterV10 struct { @@ -134,6 +149,7 @@ type encWriterV10 struct { dst io.Writer buffer packageV10 + recycle func() // Returns the buffer to the pool offset int payloadSize int closeErr error @@ -150,10 +166,13 @@ func encryptWriterV10(dst io.Writer, config *Config) (*encWriterV10, error) { if err != nil { return nil, err } + + buffer, recycle := getBuffer() return &encWriterV10{ authEncV10: ae, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, payloadSize: config.PayloadSize, }, nil } @@ -169,14 +188,18 @@ func (w *encWriterV10) Write(p []byte) (n int, err error) { n = copy(w.buffer[headerSize+w.offset:], p[:remaining]) w.Seal(w.buffer, w.buffer[headerSize:headerSize+w.payloadSize]) if err = flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] w.offset = 0 } for len(p) >= w.payloadSize { - w.Seal(w.buffer[:], p[:w.payloadSize]) + w.Seal(w.buffer, p[:w.payloadSize]) if err = flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[w.payloadSize:] @@ -190,25 +213,25 @@ func (w *encWriterV10) Write(p []byte) (n int, err error) { } func (w *encWriterV10) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if dst, ok := w.dst.(io.Closer); ok { + dst.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() if w.offset > 0 { w.Seal(w.buffer[:], w.buffer[headerSize:headerSize+w.offset]) - if err := flush(w.dst, w.buffer[:w.buffer.Length()]); err != nil { // write to underlying io.Writer - return err - } + w.closeErr = flush(w.dst, w.buffer[:w.buffer.Length()]) // write to underlying io.Writer } if dst, ok := w.dst.(io.Closer); ok { - return dst.Close() + if err := dst.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } func flush(w io.Writer, p []byte) error { diff --git a/writer-v2.go b/writer-v2.go index c48afd8..8f27a98 100644 --- a/writer-v2.go +++ b/writer-v2.go @@ -23,6 +23,7 @@ type encWriterV20 struct { dst io.Writer buffer packageV20 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -38,10 +39,12 @@ func encryptWriterV20(dst io.Writer, config *Config) (*encWriterV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &encWriterV20{ authEncV20: ae, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } @@ -60,6 +63,8 @@ func (w *encWriterV20) Write(p []byte) (n int, err error) { n = copy(w.buffer[headerSize+w.offset:], p[:remaining]) w.Seal(w.buffer, w.buffer[headerSize:headerSize+maxPayloadSize]) if err = flush(w.dst, w.buffer); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] @@ -68,6 +73,8 @@ func (w *encWriterV20) Write(p []byte) (n int, err error) { for len(p) > maxPayloadSize { // > is important here to call Seal (not SealFinal) only if there is at least on package left - see: Close() w.Seal(w.buffer, p[:maxPayloadSize]) if err = flush(w.dst, w.buffer); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[maxPayloadSize:] @@ -81,26 +88,26 @@ func (w *encWriterV20) Write(p []byte) (n int, err error) { } func (w *encWriterV20) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if closer, ok := w.dst.(io.Closer); ok { + closer.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() if w.offset > 0 { // true if at least one Write call happened w.SealFinal(w.buffer, w.buffer[headerSize:headerSize+w.offset]) - if err = flush(w.dst, w.buffer[:headerSize+w.offset+tagSize]); err != nil { // write to underlying io.Writer - return err - } + w.closeErr = flush(w.dst, w.buffer[:headerSize+w.offset+tagSize]) // write to underlying io.Writer w.offset = 0 } if closer, ok := w.dst.(io.Closer); ok { - return closer.Close() + if err := closer.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr } type decWriterV20 struct { @@ -108,6 +115,7 @@ type decWriterV20 struct { dst io.Writer buffer packageV20 + recycle func() // Returns the buffer to the pool offset int closeErr error } @@ -122,10 +130,12 @@ func decryptWriterV20(dst io.Writer, config *Config) (*decWriterV20, error) { if err != nil { return nil, err } + buffer, recycle := getBuffer() return &decWriterV20{ authDecV20: ad, dst: dst, - buffer: packageBufferPool.Get().([]byte)[:maxPackageSize], + buffer: buffer, + recycle: recycle, }, nil } @@ -139,9 +149,13 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { n = copy(w.buffer[w.offset:], p[:remaining]) plaintext := w.buffer[headerSize : headerSize+maxPayloadSize] if err = w.Open(plaintext, w.buffer); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, plaintext); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[remaining:] @@ -150,9 +164,13 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { for len(p) >= maxPackageSize { plaintext := w.buffer[headerSize : headerSize+maxPayloadSize] if err = w.Open(plaintext, p[:maxPackageSize]); err != nil { + w.recycle() + w.closeErr = err return n, err } if err = flush(w.dst, plaintext); err != nil { // write to underlying io.Writer + w.recycle() + w.closeErr = err return n, err } p = p[maxPackageSize:] @@ -160,7 +178,9 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { } if len(p) > 0 { if w.finalized { - return n, errUnexpectedData + w.recycle() + w.closeErr = errUnexpectedData + return n, w.closeErr } w.offset = copy(w.buffer[:], p) n += w.offset @@ -169,29 +189,32 @@ func (w *decWriterV20) Write(p []byte) (n int, err error) { } func (w *decWriterV20) Close() (err error) { - if w.buffer == nil { + defer w.recycle() + + if w.closeErr != nil { + if closer, ok := w.dst.(io.Closer); ok { + closer.Close() + } return w.closeErr } - defer func() { - w.closeErr = err - recyclePackageBufferPool(w.buffer) - w.buffer = nil - }() + if w.offset > 0 { if w.offset <= headerSize+tagSize { // the payload is always > 0 - return errInvalidPayloadSize + w.closeErr = errInvalidPayloadSize } - if err = w.Open(w.buffer[headerSize:w.offset-tagSize], w.buffer[:w.offset]); err != nil { - return err + if w.closeErr == nil { + w.closeErr = w.Open(w.buffer[headerSize:w.offset-tagSize], w.buffer[:w.offset]) } - if err = flush(w.dst, w.buffer[headerSize:w.offset-tagSize]); err != nil { // write to underlying io.Writer - return err + if w.closeErr == nil { + w.closeErr = flush(w.dst, w.buffer[headerSize:w.offset-tagSize]) // write to underlying io.Writer } w.offset = 0 } if closer, ok := w.dst.(io.Closer); ok { - return closer.Close() + if err := closer.Close(); w.closeErr == nil { + w.closeErr = err + } } - return nil + return w.closeErr }