x/sys/unix: Undocumented (possibly incorrect?) SendmsgBuffers behavior with empty iovec #56911
Labels
compiler/runtime
Issues related to the Go compiler and/or runtime.
Documentation
FrozenDueToAge
help wanted
NeedsInvestigation
Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I'm interfacing with the Linux kernel cryptography API using the AF_ALG interface, across two processes. One process is responsible for setting up an AF_ALG (SOCK_SEQPACKET) socket, including setting the key and IV, and then the resulting socket file descriptor is passed to another process (using SCM_RIGHTS), which uses it for encryption or decryption. By design, the first process does not have access to any of the plaintext or ciphertext data, and the second process does not have access to the encryption key.
The bug I was encountering was seemingly an extra '\0' in the decrypted plaintext output and an off-by-one error between plaintext and expected ciphertext length. I discovered this is because an extra byte is silently added by x/sys/unix.sendmsgN() if passed an empty Iovec but non-empty out-of-band data, on a non-SOCK_DGRAM socket:
https://cs.opensource.google/go/x/sys/+/refs/tags/v0.2.0:unix/syscall_linux.go;l=1538;drc=d684c6f886692f8b63049d3dabe4f1911ae979c3;bpv=1;bpt=1
As far as I can tell, this behavior is undocumented. I believe some socket types require at least one byte of data with OOB, but AF_ALG sockets do not. (The Linux kernel documentation references https://www.chronox.de/libkcapi.html as the canonical client implementation, and it follows the pattern of OOB-but-empty-data to set up the socket, and then it uses vmsplice() to send the non-OOB data.)
The following call results in the extra byte being sent to the socket (error handling and populating
var oob []byte
omitted):This code works as expected:
What did you expect to see?
An empty [][]byte resulting in no data being sent.
The documentation for SendmsgBuffers states, "the function returns the number of bytes written to the socket."
What did you see instead?
A '\0' byte being sent to the socket unexpectedly.
SendmsgBuffers returns 0, but actually sends 1 byte to the socket.
Possible solutions/topics for discussion
The implicit extra byte with Sendmsg appears to date back to the very first Go commit, so it probably comes from Plan 9. SendmsgBuffers is, however, relatively new (#52885) so maybe its API/behavior is not as set in stone. Whether or not their behavior in this regard should diverge is probably a topic for debate.
Should SOCK_SEQPACKET be treated the same as SOCK_DGRAM, and excluded from having the extra byte sent? If so, should the socket types (maybe just SOCK_STREAM) that require special empty-iovec handling be an allowlist instead of a denylist?
Should the SendmsgBuffers return value be updated to reflect the actual number of bytes sent when passed an empty [][]byte?
Instead of silently adding an invisible byte, should an error be returned if SendmsgBuffers is not supposed to be called with an empty [][]byte?
Even if no changes to behavior are warranted, should SendmsgBuffer's documentation be improved? (Yes, I am volunteering to do so. :))
The text was updated successfully, but these errors were encountered: