Skip to content

sockaddr_storage shouldn't have internal padding bytes #3004

Closed
@stevenengler

Description

@stevenengler

A common use-case of sockaddr_storage is to store a socket address in a sockaddr_storage, inspect the ss_family field and the socket address length, and then cast it to the correct socket address type (for example sockaddr_in). For example, this is what the nix crate currently does using a union: https://github.com/nix-rust/nix/blob/fbebb21dd8df447a1408795b4b5706d9ca6c55df/src/sys/socket/addr.rs#L1537-L1540

The rust libc library defines sockaddr_storage as the following on Linux:

pub struct sockaddr_storage {
    pub ss_family: sa_family_t,
    __ss_align: ::size_t,
    #[cfg(target_pointer_width = "32")]
    __ss_pad2: [u8; 128 - 2 * 4],
    #[cfg(target_pointer_width = "64")]
    __ss_pad2: [u8; 128 - 2 * 8],
}

On Linux x86-64, this results in the following representation:

type: `unix::linux_like::sockaddr_storage`: 128 bytes, alignment: 8 bytes
    field `.ss_family`: 2 bytes
    padding: 6 bytes
    field `.__ss_align`: 8 bytes, alignment: 8 bytes
    field `.__ss_pad2`: 112 bytes

From what I understand, the 6 padding bytes in sockaddr_storage should cause a cast from memory initialized as a sockaddr_storage to another type like sockaddr_in to invoke UB since these padding bytes are considered to be uninitialized memory. For example these padding bytes of sockaddr_storage overlap with the sockaddr_in.sin_port field, so reading the sin_port field after casting from a sockaddr_storage would be UB. And in the nix case, the ss: libc::sockaddr_storage = mem::zeroed() may also not zero these 6 padding bytes, possibly leading to UB when it's later read as a different socket address type if those padding bytes weren't overwritten during the ptr::copy.

On recent glibc versions (such as 2.31 on Ubuntu 20.04), they define sockaddr_storage as:

#define __ss_aligntype  unsigned long int
#define _SS_PADSIZE \
  (_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype))

struct sockaddr_storage
  {
    __SOCKADDR_COMMON (ss_);    /* Address family, etc.  */
    char __ss_padding[_SS_PADSIZE];
    __ss_aligntype __ss_align;  /* Force desired alignment.  */
  };

Since they have the padding/alignment fields in the opposite order from rust's libc definition (have the char array field first), there are no padding bytes:

printf("sockaddr_storage.ss_family: (offset) %ld, (size) %ld\n",
       (void *)&addr.ss_family - (void *)&addr,
       sizeof(addr.ss_family));
printf("sockaddr_storage.__ss_padding: (offset) %ld, (size) %ld\n",
       (void *)&addr.__ss_padding - (void *)&addr,
       sizeof(addr.__ss_padding));
printf("sockaddr_storage.__ss_align: (offset) %ld, (size) %ld\n",
       (void *)&addr.__ss_align - (void *)&addr,
       sizeof(addr.__ss_align));
printf("sockaddr_storage: (size) %ld\n", sizeof(addr));
sockaddr_storage.ss_family:	(offset) 0,	(size) 2
sockaddr_storage.__ss_padding:	(offset) 2,	(size) 118
sockaddr_storage.__ss_align:	(offset) 120,	(size) 8
sockaddr_storage: (size) 128

The order of these fields was changed in this 2016 glibc patch: "Make padding in struct sockaddr_storage explicit [BZ #20111]" https://patchwork.sourceware.org/project/glibc/patch/fea4fa60-be9c-7cc2-b09c-c48845fe603f@redhat.com/

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions