{"record":{"id":"c522ed0b25f5767a","repo":"FiloSottile/age","slug":"offset-out-of-range-0-d-d","errorCode":null,"errorMessage":"offset out of range [0:%d]: %d","messagePattern":"offset out of range \\[0:(.+?)\\]: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/stream.go","lineNumber":414,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to read final chunk: %w\", err)\n\t}\n\tnonce := nonceForChunk(finalChunkIndex)\n\tsetLastChunkFlag(nonce)\n\tplaintext, err := aead.Open(finalChunk[:0], nonce[:], finalChunk, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decrypt and authenticate final chunk: %w\", err)\n\t}\n\tcache := &cachedChunk{off: finalChunkOff, data: plaintext}\n\n\tplaintextSize := size - chunks*chacha20poly1305.Overhead\n\tr := &DecryptReaderAt{a: aead, src: src, size: plaintextSize, chunks: chunks}\n\tr.cache.Store(cache)\n\treturn r, nil\n}\n\nfunc (r *DecryptReaderAt) ReadAt(p []byte, off int64) (n int, err error) {\n\tif off < 0 || off > r.size {\n\t\treturn 0, fmt.Errorf(\"offset out of range [0:%d]: %d\", r.size, off)\n\t}\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\tvar cacheUpdate *cachedChunk\n\tchunk := make([]byte, encChunkSize)\n\tfor len(p) > 0 && off < r.size {\n\t\tchunkIndex := off / ChunkSize\n\t\tchunkOff := chunkIndex * encChunkSize\n\t\tencSize := r.size + r.chunks*chacha20poly1305.Overhead\n\t\tchunkSize := min(encSize-chunkOff, encChunkSize)\n\n\t\tcached := r.cache.Load()\n\t\tvar plaintext []byte\n\t\tif cached != nil && cached.off == chunkOff {\n\t\t\tplaintext = cached.data\n\t\t\tcacheUpdate = nil\n\t\t} else {","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/stream/stream.go#L396-L432","documentation":"DecryptReaderAt.ReadAt rejects offsets outside the valid plaintext range [0, size]. This mirrors io.ReaderAt conventions: off must be >= 0 and <= plaintext size (off == size is allowed and yields io.EOF for non-empty reads). The library returns a descriptive range error rather than panicking or wrapping negative indices.","triggerScenarios":"Calling ReadAt(p, off) with off < 0; or with off > r.size (beyond the plaintext length). Note off == r.size is accepted but immediately hits the EOF path once len(p) > 0.","commonSituations":"io.SectionReader or offset math gone wrong; passing the ciphertext offset instead of the plaintext offset; off-by-one loops with stale size values; concurrent readers sharing a stale size after a re-encryption.","solutions":["Clamp or validate off against the plaintext size returned by stream.PlaintextSize(encryptedSize) before calling ReadAt.","Use io.NewSectionReader(r, 0, plaintextSize) to get automatic bounds enforcement and correct EOF semantics.","Check you are not passing ciphertext offsets; ReadAt operates in plaintext coordinates.","Fix loops so they terminate at off == size (which yields io.EOF) rather than advancing past it."],"exampleFix":"// before\nbuf := make([]byte, 100)\nn, err := dr.ReadAt(buf, -1)\n// after\nif off < 0 || off > size {\n    return 0, fmt.Errorf(\"offset %d out of range\", off)\n}\nn, err := dr.ReadAt(buf, off)","handlingStrategy":"validation","validationCode":"plainSize, err := stream.PlaintextSize(encryptedSize)\nif err != nil { return err }\nif off < 0 || off > plainSize {\n    return fmt.Errorf(\"offset %d out of [0:%d]\", off, plainSize)\n}","typeGuard":null,"tryCatchPattern":"n, err := dr.ReadAt(p, off)\nif err != nil && !errors.Is(err, io.EOF) {\n    if strings.Contains(err.Error(), \"offset out of range\") {\n        return fmt.Errorf(\"caller bug: bad offset %d\", off)\n    }\n    return err\n}","preventionTips":["Wrap the reader in io.NewSectionReader(dr, 0, plainSize) for automatic bounds checks.","Remember ReadAt uses plaintext offsets, not ciphertext offsets.","Bound loops at off == size and rely on io.EOF for termination."],"tags":["stream","bounds-check","readerat","go"],"backgroundTag":"offset-out-of-range","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}