In oggopus/decoder.go Decoder.fill(), the granule end-trim computes keep := max(int(d.limit-d.delivered), 0) and then slices samples[:keep*d.info.Channels]. The int64 delta d.limit - d.delivered is converted to int before the clamp.
In normal decoding this is safe. The clamp only runs under d.limit >= 0 && d.delivered+perChan > d.limit, and delivered never exceeds limit (once end-trim fires, delivered is pinned to limit), so the delta is in [0, perChan) where perChan is a single Opus frame (at most 5760 samples per channel). That fits int32 on every platform, and on 64-bit int is 64-bit anyway, so there is no way to reach a bad value.
The theoretical gap is 32-bit only (GOARCH=386/arm). The total-sample limit is discovered lazily (it stays -1 until totalSamples() returns). If a malformed or adversarial stream withholds the limit until after more than ~2^31 samples per channel have already been delivered, and the eventually-discovered limit is smaller than delivered, then d.limit - d.delivered is a large negative int64 whose int() conversion can wrap to a positive int on a 32-bit build. That defeats the max(..., 0) floor and yields an oversized slice bound in samples[:keep*d.info.Channels], i.e. a panic while decoding untrusted input.
This predates the modernize change in #88: that PR only replaced an equivalent if keep < 0 { keep = 0 } clamp with max() and did not alter the conversion order. The observation was raised by Copilot on #88.
Suggested fix: clamp in int64 and cap to perChan before converting to int, for example:
keep := int(min(max(d.limit-d.delivered, 0), int64(perChan)))
Severity is low (32-bit targets only, and only under a specifically malformed late-limit stream), but go-opus decodes untrusted Ogg Opus input, so not panicking on a malformed stream regardless of word size is the right property.
In
oggopus/decoder.goDecoder.fill(), the granule end-trim computeskeep := max(int(d.limit-d.delivered), 0)and then slicessamples[:keep*d.info.Channels]. Theint64deltad.limit - d.deliveredis converted tointbefore the clamp.In normal decoding this is safe. The clamp only runs under
d.limit >= 0 && d.delivered+perChan > d.limit, anddeliverednever exceedslimit(once end-trim fires,deliveredis pinned tolimit), so the delta is in[0, perChan)whereperChanis a single Opus frame (at most 5760 samples per channel). That fitsint32on every platform, and on 64-bitintis 64-bit anyway, so there is no way to reach a bad value.The theoretical gap is 32-bit only (
GOARCH=386/arm). The total-samplelimitis discovered lazily (it stays-1untiltotalSamples()returns). If a malformed or adversarial stream withholds the limit until after more than ~2^31 samples per channel have already been delivered, and the eventually-discovered limit is smaller thandelivered, thend.limit - d.deliveredis a large negativeint64whoseint()conversion can wrap to a positiveinton a 32-bit build. That defeats themax(..., 0)floor and yields an oversized slice bound insamples[:keep*d.info.Channels], i.e. a panic while decoding untrusted input.This predates the modernize change in #88: that PR only replaced an equivalent
if keep < 0 { keep = 0 }clamp withmax()and did not alter the conversion order. The observation was raised by Copilot on #88.Suggested fix: clamp in
int64and cap toperChanbefore converting toint, for example:Severity is low (32-bit targets only, and only under a specifically malformed late-limit stream), but go-opus decodes untrusted Ogg Opus input, so not panicking on a malformed stream regardless of word size is the right property.