mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Cap initial usermem.CopyStringIn buffer size.
Almost (?) all uses of CopyStringIn are via linux.copyInPath(), which passes maxlen = linux.PATH_MAX = 4096. Pre-allocating a buffer of this size is measurably inefficient in most cases: most paths will not be this long, 4 KB is a lot of bytes to zero, and as of this writing the Go runtime allocator maps only two 4 KB objects to each 8 KB span, necessitating a call to runtime.mcache.refill() on ~every other call. Limit the initial buffer size to 256 B instead, and geometrically reallocate if necessary. PiperOrigin-RevId: 251960441
This commit is contained in:
@@ -222,9 +222,11 @@ func CopyObjectIn(ctx context.Context, uio IO, addr Addr, dst interface{}, opts
|
||||
return int(r.Addr - addr), nil
|
||||
}
|
||||
|
||||
// copyStringIncrement is the maximum number of bytes that are copied from
|
||||
// virtual memory at a time by CopyStringIn.
|
||||
const copyStringIncrement = 64
|
||||
// CopyStringIn tuning parameters, defined outside that function for tests.
|
||||
const (
|
||||
copyStringIncrement = 64
|
||||
copyStringMaxInitBufLen = 256
|
||||
)
|
||||
|
||||
// CopyStringIn copies a NUL-terminated string of unknown length from the
|
||||
// memory mapped at addr in uio and returns it as a string (not including the
|
||||
@@ -234,31 +236,38 @@ const copyStringIncrement = 64
|
||||
//
|
||||
// Preconditions: As for IO.CopyFromUser. maxlen >= 0.
|
||||
func CopyStringIn(ctx context.Context, uio IO, addr Addr, maxlen int, opts IOOpts) (string, error) {
|
||||
buf := make([]byte, maxlen)
|
||||
initLen := maxlen
|
||||
if initLen > copyStringMaxInitBufLen {
|
||||
initLen = copyStringMaxInitBufLen
|
||||
}
|
||||
buf := make([]byte, initLen)
|
||||
var done int
|
||||
for done < maxlen {
|
||||
start, ok := addr.AddLength(uint64(done))
|
||||
if !ok {
|
||||
// Last page of kernel memory. The application can't use this
|
||||
// anyway.
|
||||
return stringFromImmutableBytes(buf[:done]), syserror.EFAULT
|
||||
}
|
||||
// Read up to copyStringIncrement bytes at a time.
|
||||
readlen := copyStringIncrement
|
||||
if readlen > maxlen-done {
|
||||
readlen = maxlen - done
|
||||
}
|
||||
end, ok := start.AddLength(uint64(readlen))
|
||||
end, ok := addr.AddLength(uint64(readlen))
|
||||
if !ok {
|
||||
return stringFromImmutableBytes(buf[:done]), syserror.EFAULT
|
||||
}
|
||||
// Shorten the read to avoid crossing page boundaries, since faulting
|
||||
// in a page unnecessarily is expensive. This also ensures that partial
|
||||
// copies up to the end of application-mappable memory succeed.
|
||||
if start.RoundDown() != end.RoundDown() {
|
||||
if addr.RoundDown() != end.RoundDown() {
|
||||
end = end.RoundDown()
|
||||
readlen = int(end - addr)
|
||||
}
|
||||
n, err := uio.CopyIn(ctx, start, buf[done:done+int(end-start)], opts)
|
||||
// Ensure that our buffer is large enough to accommodate the read.
|
||||
if done+readlen > len(buf) {
|
||||
newBufLen := len(buf) * 2
|
||||
if newBufLen > maxlen {
|
||||
newBufLen = maxlen
|
||||
}
|
||||
buf = append(buf, make([]byte, newBufLen-len(buf))...)
|
||||
}
|
||||
n, err := uio.CopyIn(ctx, addr, buf[done:done+readlen], opts)
|
||||
// Look for the terminating zero byte, which may have occurred before
|
||||
// hitting err.
|
||||
for i, c := range buf[done : done+n] {
|
||||
@@ -270,6 +279,7 @@ func CopyStringIn(ctx context.Context, uio IO, addr Addr, maxlen int, opts IOOpt
|
||||
if err != nil {
|
||||
return stringFromImmutableBytes(buf[:done]), err
|
||||
}
|
||||
addr = end
|
||||
}
|
||||
return stringFromImmutableBytes(buf), syserror.ENAMETOOLONG
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ func TestCopyObject(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopyStringInShort(t *testing.T) {
|
||||
// Tests for string length <= copyStringIncrement.
|
||||
want := strings.Repeat("A", copyStringIncrement-2)
|
||||
mem := want + "\x00"
|
||||
if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringIncrement, IOOpts{}); got != want || err != nil {
|
||||
@@ -200,13 +201,25 @@ func TestCopyStringInShort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopyStringInLong(t *testing.T) {
|
||||
want := strings.Repeat("A", copyStringIncrement+1)
|
||||
// Tests for copyStringIncrement < string length <= copyStringMaxInitBufLen
|
||||
// (requiring multiple calls to IO.CopyIn()).
|
||||
want := strings.Repeat("A", copyStringIncrement*3/4) + strings.Repeat("B", copyStringIncrement*3/4)
|
||||
mem := want + "\x00"
|
||||
if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringIncrement, IOOpts{}); got != want || err != nil {
|
||||
t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, nil)", got, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyStringInVeryLong(t *testing.T) {
|
||||
// Tests for string length > copyStringMaxInitBufLen (requiring buffer
|
||||
// reallocation).
|
||||
want := strings.Repeat("A", copyStringMaxInitBufLen*3/4) + strings.Repeat("B", copyStringMaxInitBufLen*3/4)
|
||||
mem := want + "\x00"
|
||||
if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringMaxInitBufLen, IOOpts{}); got != want || err != nil {
|
||||
t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, nil)", got, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyStringInNoTerminatingZeroByte(t *testing.T) {
|
||||
want := strings.Repeat("A", copyStringIncrement-1)
|
||||
got, err := CopyStringIn(newContext(), newBytesIOString(want), 0, 2*copyStringIncrement, IOOpts{})
|
||||
|
||||
Reference in New Issue
Block a user