netlink getsockopt() sets only one byte?

From: Brad Spencer
Date: Tue Apr 18 2023 - 14:07:18 EST


Calling getsockopt() on a netlink socket with SOL_NETLINK options that
use type int only sets the first byte of the int value but returns an
optlen equal to sizeof(int), at least on x86_64.


The detailed description:

It looks like netlink_getsockopt() calls put_user() with a char*
pointer, and I think that causes it to copy only one byte from the val
result, despite len being sizeof(int).

Is this the expected behaviour? The returned size is 4, after all,
and other int-sized socket options (outside of netlink) like
SO_REUSEADDR set all bytes of the int.

Programs that do not expect this behaviour and do not initialize the
value to some known bit pattern are likely to misinterpret the result,
especially when checking to see if the value is or isn't zero.

Attached is a short program that demonstrates the issue on Arch Linux
with the 6.3.0-rc6 mainline kernel on x86_64, and also with the same
Arch Linux userland on 6.2.10-arch1-1. I've seen the same behaviour
on older Debian and Ubuntu kernels.

gcc -Wall -o prog prog.c

Show only the first byte being written to when the setting is `0`:

$ ./progboot
SOL_SOCKET SO_REUSEADDR:
size=4 value=0x0
SOL_NETLINK NETLINK_NO_ENOBUFS:
size=4 value=0xdeadbe00
prog: prog.c:39: tryOption: Assertion `value == 0' failed.
Aborted (core dumped)

Workaround by initializing to zero:

$ ./prog workaround
SOL_SOCKET SO_REUSEADDR:
size=4 value=0x0
SOL_NETLINK NETLINK_NO_ENOBUFS:
size=4 value=0x0

Show only the first byte being written to when the setting is `1`:

$ SET_FIRST=yes ./prog
SOL_SOCKET SO_REUSEADDR:
size=4 value=0x1
SOL_NETLINK NETLINK_NO_ENOBUFS:
size=4 value=0xdeadbe01
prog: prog.c:35: tryOption: Assertion `value == 1' failed.
Aborted (core dumped)

Workaround by initializing to zero:

$ SET_FIRST=yes ./prog workaround
SOL_SOCKET SO_REUSEADDR:
size=4 value=0x1
SOL_NETLINK NETLINK_NO_ENOBUFS:
size=4 value=0x1


Demonstration program:

#include <asm/types.h>
#include <assert.h>
#include <linux/netlink.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <unistd.h>

static void tryOption(const int fd,
const int level,
const int optname,
const int workaround,
const int setFirst)
{
assert(fd != -1);

// Setting it to 1 gives similar results.
if(setFirst)
{
int value = 1;
assert(!setsockopt(fd, level, optname, &value, sizeof(value)));
}

// Get the option.
{
int value = workaround ? 0 : 0xdeadbeef;
socklen_t size = sizeof(value);

// Only the first byte of `value` is written to!
assert(!getsockopt(fd, level, optname, &value, &size));
printf("size=%u value=0x%x\n", size, value);
if(setFirst)
{
assert(value == 1);
}
else
{
assert(value == 0);
}

// But it always reports a 4 byte option size.
assert(size == sizeof(int));
}

close(fd);
}

int
main(int argc, char** argv)
{
// If any argument is supplied, apply a workaround.
const int workaround = argc > 1;

// If $SET_FIRST is set to anything, set the option to 1 first.
const int setFirst = getenv("SET_FIRST") != NULL;

// Other int options set all bytes of the int.
printf("SOL_SOCKET SO_REUSEADDR:\n");
tryOption(
socket(AF_INET, SOCK_STREAM, 0),
SOL_SOCKET,
SO_REUSEADDR,
workaround,
setFirst);

// Netlink int socket options do not set all bytes of the int.
printf("SOL_NETLINK NETLINK_NO_ENOBUFS:\n");
tryOption(
socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE),
SOL_NETLINK,
NETLINK_NO_ENOBUFS,
workaround,
setFirst);
}

--
Brad Spencer

----------------------------------------------------------------------
This transmission (including any attachments) may contain confidential information, privileged material (including material protected by the solicitor-client or other applicable privileges), or constitute non-public information. Any use of this information by anyone other than the intended recipient is prohibited. If you have received this transmission in error, please immediately reply to the sender and delete this information from your system. Use, dissemination, distribution, or reproduction of this transmission by unintended recipients is not authorized and may be unlawful.