-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix memory corruption #25
Conversation
Ugh. How on earth is this possible. I spent a million years looking at these docs and here we are. Thanks for the patch. I'll have a look over tonight, but it looks like you're right. Please also add yourself to the AUTHORS list. |
Done. |
Yikes. Thanks @gavv! |
Hi gavv I don't have time to look at the cap vs len thing for at least another week. I like the idea but I need to think through the possible implications for people already depending on this code. In the mean time, especially now that this bug is out in the open, I'd like to merge the memory issue. Would you mean removing the len/cap code from this PR and creating another one? Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! 👍
decoder.go
Outdated
@@ -74,12 +76,15 @@ func (dec *Decoder) Decode(data []byte, pcm []int16) (int, error) { | |||
if len(pcm) == 0 { | |||
return 0, fmt.Errorf("opus: target buffer empty") | |||
} | |||
if len(pcm)%dec.channels != 0 { | |||
return 0, fmt.Errorf("opus: output buffer length must be multiple of channels") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return 0, fmt.Errorf("opus: output buffer length must be multiple of channels") | |
return 0, fmt.Errorf("opus: target buffer length must be multiple of channels") |
decoder.go
Outdated
@@ -99,12 +104,15 @@ func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error) { | |||
if len(pcm) == 0 { | |||
return 0, fmt.Errorf("opus: target buffer empty") | |||
} | |||
if len(pcm)%dec.channels != 0 { | |||
return 0, fmt.Errorf("opus: output buffer length must be multiple of channels") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return 0, fmt.Errorf("opus: output buffer length must be multiple of channels") | |
return 0, fmt.Errorf("opus: target buffer length must be multiple of channels") |
decoder.go
Outdated
@@ -125,13 +133,15 @@ func (dec *Decoder) DecodeFEC(data []byte, pcm []int16) error { | |||
if len(pcm) == 0 { | |||
return fmt.Errorf("opus: target buffer empty") | |||
} | |||
|
|||
if len(pcm)%dec.channels != 0 { | |||
return fmt.Errorf("opus: output buffer length must be multiple of channels") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("opus: output buffer length must be multiple of channels") | |
return fmt.Errorf("opus: target buffer length must be multiple of channels") |
decoder.go
Outdated
@@ -152,12 +162,15 @@ func (dec *Decoder) DecodeFECFloat32(data []byte, pcm []float32) error { | |||
if len(pcm) == 0 { | |||
return fmt.Errorf("opus: target buffer empty") | |||
} | |||
if len(pcm)%dec.channels != 0 { | |||
return fmt.Errorf("opus: output buffer length must be multiple of channels") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("opus: output buffer length must be multiple of channels") | |
return fmt.Errorf("opus: target buffer length must be multiple of channels") |
t.Run("larger-buffer-float32-fec", func(t *testing.T) { | ||
decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE+1, false) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*sigh, go and generics...
decoder.go
Outdated
n := int(C.opus_decode( | ||
dec.p, | ||
(*C.uchar)(&data[0]), | ||
C.opus_int32(len(data)), | ||
(*C.opus_int16)(&pcm[0]), | ||
C.int(cap(pcm)), | ||
C.int(len(pcm)/dec.channels), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C.int(len(pcm)/dec.channels), | |
C.int(cap(pcm)/dec.channels), |
sticking to cap for this PR, will think on cap/len later.
decoder.go
Outdated
n := int(C.opus_decode_float( | ||
dec.p, | ||
(*C.uchar)(&data[0]), | ||
C.opus_int32(len(data)), | ||
(*C.float)(&pcm[0]), | ||
C.int(cap(pcm)), | ||
C.int(len(pcm)/dec.channels), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C.int(len(pcm)/dec.channels), | |
C.int(cap(pcm)/dec.channels), |
decoder.go
Outdated
n := int(C.opus_decode( | ||
dec.p, | ||
(*C.uchar)(&data[0]), | ||
C.opus_int32(len(data)), | ||
(*C.opus_int16)(&pcm[0]), | ||
C.int(cap(pcm)), | ||
C.int(len(pcm)/dec.channels), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C.int(len(pcm)/dec.channels), | |
C.int(cap(pcm)/dec.channels), |
decoder.go
Outdated
n := int(C.opus_decode_float( | ||
dec.p, | ||
(*C.uchar)(&data[0]), | ||
C.opus_int32(len(data)), | ||
(*C.float)(&pcm[0]), | ||
C.int(cap(pcm)), | ||
C.int(len(pcm)/dec.channels), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C.int(len(pcm)/dec.channels), | |
C.int(cap(pcm)/dec.channels), |
Forget about the github UI, I've done it old school locally and just pushed it. |
I'd suggest you to make a v3 release. This patch, even with cap() instead of len(), breaks the API anyway.
Sure. |
I've revered cap/len change, applied your suggestions, and fixed tests. |
I will have to backport this to v2 and v1 because it's a security issue (for future reference, by the way: if you find memory corruption bug, I recommend reporting it in private at least initially, because putting it out in the open significantly increases the pressure to fix it asap :/ it's not ideal timing at the mo 😭). |
Isn't the error in our invocation of libopus? as far as I can see, fixing this should work backwards compatibly for everyone. I was calling libopus wrong, but users of this lib just pass in an array with a cap, no further updates needed (cue also the other tests which still work fine). Any specific reason you were thinking of a v3? |
You're right. I was thinking about the behavior change. There are three cases:
Old behavior:
New behavior:
So we're changing a (probably silent) memory corruption to an error. I guess this should be considered as a compatible change :) |
I'd like to push more commits into v2 branch of my fork, which are not related to this PR. Github does not allow me to switch PR's branch, so I'm reopening it with another branch: #26. I've also replaced len() with cap() in new buffer size checks. I didn't touch the old checks. |
Hi and thanks for sharing this library.
Currently, all decode methods have two problems:
They pass to opus_decode() the total number of samples in output buffer. But according to documentation, they should pass number of samples per channel. In result, in two-channel mode opus will think that the buffer is twice larger than it is. This may lead to memory corruption which is actually reproducing in my application. It's not reproduced each time. It seems that opus usually determines the frame size from its contents and doesn't touch the memory beyond that size.
They use cap() instead of len(). I believe this is a bad decision for API because if I pass
data[:n]
to a function, I expect that the memory beyondn
will not be touched. But with current API it may happen if the slice capacity is larger than the slice length. Such bugs may be very hard to find, especially if your slice comes from some other component and refers to a part of a larger memory region (e.g. in pool).This patch fixes both issues. If you don't agree with the second point, which may be arguable, I can revert the second part of the fix and use cap() instead of len().
I've also added a test. This test fails before applying the patch and succeeds after applying it.