Files
Jamie LiuandgVisor bot 94f3d8a792 Fix splices to FDs that call usermem.IO.CopyIn/CopyInTo more than once.
Fixes #9932.

When Go is able to detect `io.Copy()` from a TCP socket or `AF_UNIX` stream
socket to a TCP socket, it attempts to implement the copy as a `splice(2)` from
the source to a pipe, followed by a `splice(2)` from the pipe to the
destination [1] (since `splice(2)` requires that one of the endpoints be a
pipe); the size of the pipe is set to 1 MB [2] (from a default of 64 KB [3]) to
reduce the number of splice syscalls required. In gVisor, a bug causes each
splice syscall from pipe to TCP socket to repeatedly read the *first* 64 KB [4]
of the pipe's data (when it contains more than 64 KB of data) rather than
*successive* chunks of 64 KB.

To fix this, advance pipe state by calling `Pipe.consumeLocked()` immediately
after `Pipe.peekLocked()`. Also defensively check that such FDs call
`Pipe.(usermem.IO)` methods on sequential addresses, and change
`fuse.deviceFD.Write()` to have this property.

[1] Go: `net/tcpsock_posix.go:TCPConn.readFrom()` =>
`net/splice_linux.go:splice()` => `internal/poll/splice_linux.go:Splice()`

[2] Go: `internal/poll/splice_linux.go:newPipe()` => `maxSpliceSize`

[3] `pkg/kernel/pipe/pipe.go:DefaultPipeSize`

[4] `pkg/tcpip/transport/tcp/endpoint.go:endpoint.Write()` =>
`endpoint.queueSegment()` => `endpoint.readFromPayloader()` =>
`pkg/buffer/buffer.go:Buffer.WriteFromReader()` =>
`pkg/buffer/chunk.go:MaxChunkSize`

PiperOrigin-RevId: 603151951
2024-01-31 14:02:18 -08:00

79 lines
1.6 KiB
Python

load("//pkg/sync/locking:locking.bzl", "declare_mutex")
load("//tools:defs.bzl", "go_library", "go_test")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
declare_mutex(
name = "vfs_mutex",
out = "vfs_mutex.go",
package = "pipe",
prefix = "vfs",
)
declare_mutex(
name = "pipe_mutex",
out = "pipe_mutex.go",
nested_lock_names = ["pipe"],
package = "pipe",
prefix = "pipe",
)
declare_mutex(
name = "inode_mutex",
out = "inode_mutex.go",
package = "pipe",
prefix = "inode",
)
go_library(
name = "pipe",
srcs = [
"inode_mutex.go",
"pipe.go",
"pipe_mutex.go",
"pipe_unsafe.go",
"pipe_util.go",
"save_restore.go",
"vfs.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
"//pkg/log",
"//pkg/marshal/primitive",
"//pkg/safemem",
"//pkg/sentry/arch",
"//pkg/sentry/fsutil",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/sync/locking",
"//pkg/usermem",
"//pkg/waiter",
"@org_golang_x_sys//unix:go_default_library",
],
)
go_test(
name = "pipe_test",
size = "small",
srcs = [
"pipe_test.go",
],
library = ":pipe",
deps = [
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/sentry/contexttest",
"//pkg/sentry/vfs",
"//pkg/usermem",
"//pkg/waiter",
],
)