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

Set the length of a socket address when calling recvmsg on Linux #2041

Merged
merged 11 commits into from
Jul 17, 2023

Conversation

JarredAllen
Copy link
Contributor

Background

I think I've found a bug with recvmsg on Linux where it doesn't set the length of a received socket address. I found this while working with unix datagram sockets with path struct sockaddr_un addresses, but I think this applies to any variable-length socket address type.

At present, the sun_len field of UnixAddr on Linux shows up as 0 whenever I call recvmsg. I think it's reading uninitialized memory (afaict the recvmsg function never initializes it before we call MaybeUninit::assume_init), though it's possible that it's being zero-initialized somewhere that I missed. Either way, this isn't the correct behavior, which should set it to the length of the struct sockaddr_un contents (or whatever other type of socket).

Changes

I changed the recvmsg function to construct the returned socket address from the struct sockaddr and length returned by the libc recvmsg call (using the from_raw function the trait provides). Since the trait is sealed so downstream crates can't implement it, I believe this shouldn't be a breaking change.

Validation

I've tested that my code (which failed due to this bug) now works. I also added a new test case which tests that we construct UnixAddrs correctly in the recvmsg function, which passes locally for me and fails if I cherry-pick it onto the current master.

I've also checked that cargo test and cargo clippy both still pass on aarch64-apple-darwin and on aarch64-unknown-linux-musl targets (the two targets I develop for/have access to). Hopefully your CI will confirm that everything else still works.

Copy link
Member

@asomers asomers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that this change results in an extra data copy, in S::from_raw. Instead, what about adding a SockaddrLike::set_len method? recvmsg would call it after libc::recv_msg. For most socket types, it would be a noop, but for UnixAddr on Linux it would set the sun_len field.

Comment on lines 232 to 233
print!("Couldn't send ({e:?}), so skipping test");
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print!("Couldn't send ({e:?}), so skipping test");
return;
skip!("Couldn't send ({e:?}), so skipping test");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 72a2f56

@@ -2048,20 +2047,23 @@ pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'i
where S: SockaddrLike + 'a,
'inner: 'outer
{
let mut address = mem::MaybeUninit::uninit();
let mut address: libc::sockaddr_storage = unsafe { mem::MaybeUninit::zeroed().assume_init() };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use sockaddr_storage instead of S?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't pass tests when I used S, I think because () is shorter than the actual socket address type so it ran out of room (though I didn't check for the cause, so I might be wrong about that). Though the point is moot as I put it back to how it was before for implementing your suggestion about the different approach.

@JarredAllen
Copy link
Contributor Author

JarredAllen commented May 22, 2023

Instead, what about adding a SockaddrLike::set_len method? recvmsg would call it after libc::recv_msg.

I implemented that change, though I wasn't sure if you wanted it to be publicly-visible or not. I can hide it from the docs and mark it an internal implementation detail, if you prefer.

Also, I made it return an error if you try to set the length on any socket address type whose length is static (anything other that UnixAddr or SockaddrStorage containing a UnixAddr internally), but I could make it validate the length or some other behavior instead, if you'd prefer that.

@JarredAllen JarredAllen requested a review from asomers May 22, 2023 19:48
Copy link
Member

@asomers asomers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this version a lot better. Just a few inline comments, and:

  • Please do add #[doc(hidden)] to set_length, as you suggested, and
  • Please add a CHANGELOG entry.


/// Set the length of this socket address
///
/// This method may only be called on socket addresses whose lenghts are dynamic, and it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This method may only be called on socket addresses whose lenghts are dynamic, and it
/// This method may only be called on socket addresses whose lengths are dynamic, and it

Comment on lines 1418 to 1430
cfg_if! {
if #[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
))] {
addr.sun_len = new_length as u8;
} else {
addr.sun.sun_len = new_length as u8;
}
}
Ok(())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cfg_if! {
if #[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
))] {
addr.sun_len = new_length as u8;
} else {
addr.sun.sun_len = new_length as u8;
}
}
Ok(())
addr.set_length(new_length)

///
/// `new_length` must be a valid length for this type of address. Specifically, reads of that
/// length from `self` must be valid.
unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add a default implementation here, then you can avoid a lot of boilerplate elsewhere.

Suggested change
unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic>;
unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
Err(SocketAddressLengthNotDynamic)
}

@@ -2048,7 +2053,7 @@ pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'i
where S: SockaddrLike + 'a,
'inner: 'outer
{
let mut address = mem::MaybeUninit::uninit();
let mut address = mem::MaybeUninit::zeroed();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think it's necessary to zero the address here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally thought it might be necessary when I first started working on this (since the length was being left uninitialized initially), but looking back at it now I think everything is initialized now so I put it back to uninit()

@JarredAllen
Copy link
Contributor Author

I like this version a lot better. Just a few inline comments, and:

* Please do add `#[doc(hidden)]` to `set_length`, as you suggested, and

* Please add a CHANGELOG entry.

I've done those changes, please let me know if there's anything else you want to see.

@JarredAllen JarredAllen requested a review from asomers July 17, 2023 18:38
src/sys/socket/addr.rs Outdated Show resolved Hide resolved
Co-authored-by: Alan Somers <asomers@gmail.com>
@JarredAllen JarredAllen requested a review from asomers July 17, 2023 21:45
@asomers
Copy link
Member

asomers commented Jul 17, 2023

The linter isn't happy with that change.

@JarredAllen
Copy link
Contributor Author

The linter isn't happy with that change.

Fixed! I had to make an allowance because the variable was only used on some platforms and was left unused on others.

Copy link
Member

@asomers asomers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bors r+

@bors bors bot merged commit c375a10 into nix-rust:master Jul 17, 2023
40 checks passed
asomers pushed a commit to asomers/nix that referenced this pull request Aug 27, 2023
2041: Set the length of a socket address when calling `recvmsg` on Linux r=asomers a=JarredAllen

# Background
I think I've found a bug with `recvmsg` on Linux where it doesn't set the length of a received socket address. I found this while working with unix datagram sockets with path `struct sockaddr_un` addresses, but I think this applies to any variable-length socket address type.

At present, the `sun_len` field of `UnixAddr` on Linux shows up as 0 whenever I call `recvmsg`. I think it's reading uninitialized memory (afaict the `recvmsg` function never initializes it before we call `MaybeUninit::assume_init`), though it's possible that it's being zero-initialized somewhere that I missed. Either way, this isn't the correct behavior, which should set it to the length of the `struct sockaddr_un` contents (or whatever other type of socket).

# Changes
I changed the `recvmsg` function to construct the returned socket address from the `struct sockaddr` and length returned by the libc `recvmsg` call (using the `from_raw` function the trait provides). Since the trait is sealed so downstream crates can't implement it, I believe this shouldn't be a breaking change.

# Validation
I've tested that my code (which failed due to this bug) now works. I also added a new test case which tests that we construct `UnixAddr`s correctly in the `recvmsg` function, which passes locally for me and fails if I cherry-pick it onto the current `master`.

I've also checked that `cargo test` and `cargo clippy` both still pass on `aarch64-apple-darwin` and on `aarch64-unknown-linux-musl` targets (the two targets I develop for/have access to). Hopefully your CI will confirm that everything else still works.

Co-authored-by: Jarred Allen <jarred@moveparallel.com>
Co-authored-by: Jarred Allen <jarredallen73@gmail.com>
asomers pushed a commit to asomers/nix that referenced this pull request Aug 27, 2023
2041: Set the length of a socket address when calling `recvmsg` on Linux r=asomers a=JarredAllen

# Background
I think I've found a bug with `recvmsg` on Linux where it doesn't set the length of a received socket address. I found this while working with unix datagram sockets with path `struct sockaddr_un` addresses, but I think this applies to any variable-length socket address type.

At present, the `sun_len` field of `UnixAddr` on Linux shows up as 0 whenever I call `recvmsg`. I think it's reading uninitialized memory (afaict the `recvmsg` function never initializes it before we call `MaybeUninit::assume_init`), though it's possible that it's being zero-initialized somewhere that I missed. Either way, this isn't the correct behavior, which should set it to the length of the `struct sockaddr_un` contents (or whatever other type of socket).

# Changes
I changed the `recvmsg` function to construct the returned socket address from the `struct sockaddr` and length returned by the libc `recvmsg` call (using the `from_raw` function the trait provides). Since the trait is sealed so downstream crates can't implement it, I believe this shouldn't be a breaking change.

# Validation
I've tested that my code (which failed due to this bug) now works. I also added a new test case which tests that we construct `UnixAddr`s correctly in the `recvmsg` function, which passes locally for me and fails if I cherry-pick it onto the current `master`.

I've also checked that `cargo test` and `cargo clippy` both still pass on `aarch64-apple-darwin` and on `aarch64-unknown-linux-musl` targets (the two targets I develop for/have access to). Hopefully your CI will confirm that everything else still works.

Co-authored-by: Jarred Allen <jarred@moveparallel.com>
Co-authored-by: Jarred Allen <jarredallen73@gmail.com>
asomers pushed a commit to asomers/nix that referenced this pull request Aug 27, 2023
2041: Set the length of a socket address when calling `recvmsg` on Linux r=asomers a=JarredAllen

# Background
I think I've found a bug with `recvmsg` on Linux where it doesn't set the length of a received socket address. I found this while working with unix datagram sockets with path `struct sockaddr_un` addresses, but I think this applies to any variable-length socket address type.

At present, the `sun_len` field of `UnixAddr` on Linux shows up as 0 whenever I call `recvmsg`. I think it's reading uninitialized memory (afaict the `recvmsg` function never initializes it before we call `MaybeUninit::assume_init`), though it's possible that it's being zero-initialized somewhere that I missed. Either way, this isn't the correct behavior, which should set it to the length of the `struct sockaddr_un` contents (or whatever other type of socket).

# Changes
I changed the `recvmsg` function to construct the returned socket address from the `struct sockaddr` and length returned by the libc `recvmsg` call (using the `from_raw` function the trait provides). Since the trait is sealed so downstream crates can't implement it, I believe this shouldn't be a breaking change.

# Validation
I've tested that my code (which failed due to this bug) now works. I also added a new test case which tests that we construct `UnixAddr`s correctly in the `recvmsg` function, which passes locally for me and fails if I cherry-pick it onto the current `master`.

I've also checked that `cargo test` and `cargo clippy` both still pass on `aarch64-apple-darwin` and on `aarch64-unknown-linux-musl` targets (the two targets I develop for/have access to). Hopefully your CI will confirm that everything else still works.

Co-authored-by: Jarred Allen <jarred@moveparallel.com>
Co-authored-by: Jarred Allen <jarredallen73@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants