Subject: Re: Use of sun_len in AF_UNIX socket addresses
To: None <tech-net@NetBSD.org>
From: Christian Biere <christianbiere@gmx.de>
List: tech-net
Date: 10/11/2006 20:01:37
--rJwd6BRFiFCcLxzm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline


> Since sun_len is the sockaddr_un pun of sockaddr.sa_len, I would very much
> prefer to make sure everything sets sun_len correctly, and make the kernel use
> it properly.

With respect to userland I for one would prefer if sun_len disappeared. Many
other systems e.g., IRIX, Solaris don't have it and a lot software (maybe most)
ignores it. As NetBSD seems to ignore it and doesn't even document it, getting
rid of it seems an obvious choice to me. I don't mind what the kernel does. I
don't have a strong opinion on that though, if sun_len is there but ignored,
that's fine by me too.

> In particular, that's the only way I can see to make AF_LOCAL sockets with path
> names longer than 103 octets work properly. Most of my code that uses AF_LOCAL
> with non-null names malloc()s based on an expression rather like SUN_LEN,
> rather than allocating a struct sockaddr_un and trusting the path name to fit.

Few code does this and it's technically not portable i.e., it works only if
sun_path is last member and if the OS actually uses the trailing data. The
former can be checked at compile or runtime but it might just as well treat
sun_path as a char array that requires no NUL termination.

Actually it looks as if NetBSD doesn't require a terminating NUL if you use a
path longer than sun_path with a dynamically sized socket address as long as
you pass the correct size to bind() and connect(). Note, however, that you
mustn't use SUN_LEN() unless you allocate one additional byte and nullify it as
SUN_LEN() uses strlen().

This allows a maximum socket path of 253 characters which is still below the
minimum maximum pathname length (255) and filename length (1023) handled by
open() et al. That's because sun_len is uint8_t (u_char) like sun_family.  So
you have a limit of 255 there and the two bytes are reserved for sun_len and
sun_family. Without sun_len (in userland) you could possibly easily support
longer paths.

I don't think not terminating the path is a good choice for portable code,
though as you obviously can't check for this requirement properly.

For what it's worth, I've checked Linux and apparently it's not possible to use
a path longer than sizeof(un->sun_path). bind() returns EINVAL for paths longer
than that. FreeBSD behaves the same as NetBSD. IRIX seems to have a rather
odd maximum of 221 characters.

> It is also careful to set sun_len correctly.

Maybe but it's not documented how to set sun_len correctly. No, I don't accept
a pointer to Stevens as documentation. Also, I tried various flavours of
SUN_LEN() on IRIX and most of them were wrong causing truncation of the path.
sizeof() apparently works everywhere. It's used in NetBSD as well as
third-party code. So, I think this macro just causes unnecessary confusion or
bugs even. Every code I've seen so far uses

#ifndef SUN_LEN
#define SUN_LEN(x) ...
#endif

but as said, these are often wrong which is just never noticed because it works
on the most popular systems. As long as this is only used to set sun_len which
is seemingly ignored anyway, it's no problem.

Attached is test program that can be used to check the limit for the socket
path on a given system. Note, that this works without SUN_LEN() and sun_len. So
they are completely redundant (on NetBSD) in userland.

-- 
Christian

--rJwd6BRFiFCcLxzm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="unix.c"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

#define FREE_PTR(x) \
do { \
  if (*(x)) { \
    free(*(x)); \
    *(x) = NULL; \
  } \
} while (0)

#define STATIC_ASSERT(x) \
do { \
  switch (1) { \
  case 0 != (x): break; \
  case 0: break; \
  } \
} while (0)

#ifndef offsetof
#define offsetof(type, member) ((size_t) ((type *) 0)->member)
#endif

static void
close_fd(int *fd_ptr)
{
  if (*fd_ptr >= 0) {
    close(*fd_ptr);
    *fd_ptr = -1;
  }
}

/* Saturation arithmetic; (size_t) -1 should be treated as overflow */
static size_t
size_t_add(size_t a, size_t b)
{
  if (a > ((size_t) -1) - b)
    return (size_t) -1;
  else
    return a + b;
}

static socklen_t
calc_sockaddr_un_size(const char *path)
{
  struct sockaddr_un *addr;
  size_t size;

  if (strlen(path) < sizeof addr->sun_path) {
    return sizeof *addr;
  }

  /* Ensure that sun_path is the last member */
  STATIC_ASSERT(sizeof *addr ==
      offsetof(struct sockaddr_un, sun_path) + sizeof addr->sun_path);

  size = size_t_add(offsetof(struct sockaddr_un, sun_path), strlen(path) + 1);
  if ((socklen_t) -1 > 0) {
    if (size > (socklen_t) -1)
      return -1;
  } else if (size > INT_MAX) {
      return -1;
  }
  return (socklen_t) size;
}

static int
create_new_socket(const char *path)
{
  static const struct sockaddr_un zero_addr;
  struct sockaddr_un *addr;
  socklen_t addr_size;
  int fd;

  if (!path) {
    return -1;
  }
  fd = socket(PF_LOCAL, SOCK_STREAM, 0);
  if (-1 == fd) {
    perror("socket(PF_LOCAL, SOCK_STREAM, 0) failed");
    return -1;
  }
 
  addr_size = calc_sockaddr_un_size(path);
  if ((socklen_t) -1 == addr_size) {
    /* overflow */
    return -1;
  }

  addr = malloc(addr_size);
  *addr = zero_addr;
  addr->sun_family = AF_LOCAL;
  strncat(addr->sun_path, path, strlen(path));

  printf("sizeof(addr->sun_path)=%lu\n", (unsigned long) sizeof addr->sun_path);
  printf("strlen(addr->sun_path)=%lu\n",
      (unsigned long) strlen(addr->sun_path));
  printf("sizeof(*addr)=%lu\n", (unsigned long) sizeof *addr);
  printf("addr_size=%lu\n", (unsigned long) addr_size);
#ifdef SUN_LEN
  printf("SUN_LEN(addr)=%lu\n", (unsigned long) SUN_LEN(addr));
#endif
  printf("addr->sun_path=\"%.*s\"\n", (unsigned) strlen(path), addr->sun_path);

  if (0 != bind(fd, (const void *) addr, addr_size)) {
    perror("bind(fd, (const void *) addr, addr_size) failed");
    close_fd(&fd);
  }

  FREE_PTR(&addr);

  return fd;
}

int
main(int argc, char *argv[])
{
  if (argc != 2) {
    fprintf(stderr, "Usage: %s PATH\n", argv[0]);
    return EXIT_FAILURE;
  }
  return create_new_socket(argv[1]) >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

/* vi: set ai et ts=2 sts=2 sw=2 cindent: */

--rJwd6BRFiFCcLxzm--