Skip to content
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

x/mobile/bind/java: buffer overflow in the Java/Go parameter serialization #9251

Closed
rod-hynes opened this issue Dec 11, 2014 · 3 comments
Closed

Comments

@rod-hynes
Copy link

  • What version of Go are you using (go version)?
  • What operating system and processor architecture are you using?
    • darwin/amd64 targetting android/arm
  • What did you do?
    • Passed a 300 character string parameter from Java to Go.
  • What did you expect to see?
    • The Go function receive the parameter correctly.
  • What did you see instead?
    • Heap corruption is reported and the Android process crashes.

There appears to be a bug in the buffer resize logic in mem_write in mobile/bind/java/seq_android.c? The buffer is resized to at most 2x its previous capacity, even if the new data to be appended is much longer. Ultimately the data is written to the buffer even though the buffer is too small, and the heap is corrupted and the program crashes. In the case of a serialized string parameter, mem_write is called from Java_go_Seq_writeUTF16 which is also the function that actually writes to the undersized buffer.

To reproduce, pass a long string to Go code using the bind parameter serialization mechanism. For a function that takes a single string, the default buffer size of a Seq is 64 bytes, and mem_write will at most double it to 128 bytes, and each character requires 2 bytes, and there's a 4 byte length prefix, so any string over ~62 characters will corrupt the heap (but only significantly longer strings will crash the program consistently, a 300 character string in my test case).

In the gobind generated Java code, immediately after the _in.writeUTF16(s), for example, you can call Seq.log() to log the buffer state and observe that the len > cap. At some random point after the buffer overflow, Android logcat displays "heap corruption detected by dlmalloc" and the process terminates.

This is the current resize logic (https://github.com/golang/mobile/blob/551dbd1f07f5826104e50893b453bcf6273fbeaa/bind/java/seq_android.c#L81):

...
    if (m->off+size > m->cap) {
        m = mem_resize(m, 2*m->cap);
    }
    uint8_t *res = m->buf+m->off;
    m->off += size;
    m->len += size;
    return res;

This seems to fix it (with this change the Seq.log() output looks correct and the process doesn't crash):

...
    if (m->off+size > m->cap) {
        uint32_t cap = 2*m->cap;
        if (cap < m->off+size) {
            cap = m->off+size;
        }
        m = mem_resize(m, cap);
    }
    uint8_t *res = m->buf+m->off;
    m->off += size;
    m->len += size;
    return res;
@ThisGuyCodes
Copy link

Wouldn't you want to continue following the same methodology of doubling, instead of introducing a more arbitrary size?

...
    if (m->off+size > m->cap) {
        uint32_t cap = 2*m->cap;
        while (cap < m->off+size) {
            cap = 2*cap;
        }
        m = mem_resize(m, cap);
    }
    uint8_t *res = m->buf+m->off;
    m->off += size;
    m->len += size;
    return res;

@rod-hynes
Copy link
Author

Yes, always doubling is more in keeping with the original code.

This is a bit off the topic of the issue, but it is interesting to consider what's a good trade-off here, especially since function parameter signatures and parameter sizes may vary across usage of this facility.

Our app passes JSON data as strings from Java to Go. In the specific case of our Java->Go function calls, always doubling isn't an optimal strategy since there's only one "big" parameter. Allocating ~2x the necessary size might be a concern on a memory constrained Android device (although native code isn't as constrained as the Java VM; and, of course, once the data gets to be a certain size we'd no longer be passing it around as strings).

For what it's worth, code such as Java's ArrayList and Android Java's AbstractStringBuilder (see enlargeBuffer) appear to allocate just enough memory when trying to add additional capacity that's not within the next factor size. I think both of these cases use a 1.5x factor as well.

@crawshaw
Copy link
Member

Thanks for the detailed bug report, fix coming soon.

There are a lot of possible strategies for allocation and I agree it is not obvious what is optimal. However I suspect it is the wrong problem to focus on: when I wrote gobind I was talking to Go as a subprocess, so everything needed to be serialized. It seems entirely possible we can now avoid a lot of allocations, and write directly from seq_android.c into Go memory.

Especially for tasks like passing JSON around, where almost certainly the Java encoder is capable of producing UTF-8. The ideal type signature there would be []byte in Go and byte[] in Java, with the bytes written directly from Java to Go. (And avoiding the UTF16 -> UTF-8 conversion.)

rod-hynes added a commit to rod-hynes/psiphon-tunnel-core that referenced this issue Dec 12, 2014
@mikioh mikioh changed the title mobile/bind/java: buffer overflow in the Java/Go parameter serialization x/mobile/bind/java: buffer overflow in the Java/Go parameter serialization Aug 5, 2015
@golang golang locked and limited conversation to collaborators Aug 5, 2016
imWildCat pushed a commit to imWildCat/go-mobile that referenced this issue Apr 10, 2021
The new testLongString triggers the bug without this change.

Fixes golang/go#9251.

Change-Id: I463e2897b5b08f53801f151c7311d591546c0719
Reviewed-on: https://go-review.googlesource.com/1373
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants