NetBSD Problem Report #53692

From www@NetBSD.org  Thu Nov  1 08:45:01 2018
Return-Path: <www@NetBSD.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 7A28C7A25E
	for <gnats-bugs@gnats.NetBSD.org>; Thu,  1 Nov 2018 08:45:01 +0000 (UTC)
Message-Id: <20181101084459.F00F57A26A@mollari.NetBSD.org>
Date: Thu,  1 Nov 2018 08:44:59 +0000 (UTC)
From: johan.carlsson@enea.com
Reply-To: johan.carlsson@enea.com
To: gnats-bugs@NetBSD.org
Subject: Ephemeral port allocator can steal IPv4 port from IPv4v6 socket.
X-Send-Pr-Version: www-1.0

>Number:         53692
>Category:       misc
>Synopsis:       Ephemeral port allocator can steal IPv4 port from IPv4v6 socket.
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    misc-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Nov 01 08:50:00 +0000 2018
>Originator:     Johan Carlsson
>Release:        8.0
>Organization:
Enea Software AB
>Environment:
NetBSD localhost 8.0 NetBSD 8.0 (GENERIC) #0: Tue Jul 17 14:59:51 UTC 2018  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64

But I assume the bug has been there for a while.
>Description:
When binding an IPv6 socket that do not use V6ONLY the port for IPv4 is not reserved. It's not possible to explicitly bind them(as expected) but it is possible for the port allocator to grab the port as the next ephemeral port.

Code description:
When allocating a new ephemeral port check_suitable_port() in portalgo.c checks if the port is free for either IPv4 or IPv6. In the case where a socket is not V6ONLY and bound to any address it is still only added to the IPv6 hashtable in in6_pcb.c

This could potentially be a security issue since an application can steal another applications port and get it's packets.

Please see code to reproduce the issue.
To run the test case the default algo_bsd port allocator have to be used.
>How-To-Repeat:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <unistd.h>

int get_local_port(int socket)
{
  struct sockaddr_in so;
  int len = sizeof(so);

  if (getsockname(socket, (struct sockaddr *)&so, &len) == 0)
    {
      return ntohs(so.sin_port);
    }
}

int setup_server_v4v6(int port)
{
  struct sockaddr_in6 sa = { 0 };
  int sock;
  struct in6_addr _addr = IN6ADDR_ANY_INIT;

  if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
    perror("socket");
    return -1;
  }

  /* Make sure V6ONLY is diabled. */
  int off = 0;
  if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*) &off, sizeof(off)) == -1) {
    perror("setsockopt");
    return -1;
  }

  sa.sin6_family = AF_INET6;
  sa.sin6_port = htons(port);
  sa.sin6_addr = _addr;


  if (bind(sock, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
    perror("bind");
    return -1;
  }

  return sock;
}

int setup_server_v4(int port)
{
  struct sockaddr_in sa = { 0 };
  int len = sizeof(sa);
  int sock;

  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
    perror("socket");
    return -1;
  }
  sa.sin_family = AF_INET;
  sa.sin_port = htons(port);
  sa.sin_addr.s_addr = htonl(INADDR_ANY);

  if (bind(sock, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
    perror("bind");
    return -1;
  }

  return sock;
}

int setup_client(int port) {
  struct sockaddr_in addr = { 0 };
  memset(&addr, 0, sizeof(addr));
  int sock;

  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
    perror("socket");
    return -1;
  }

  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = htonl(0x7f000001);

  if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
    perror("connect");
    return -1;
  }
  int addrlen = sizeof(addr);
  return sock;
}

int main(int argc, char *argv[])
{
  int port;

  /* Sample the ephemeral port by setting port to 0*/
  int sample_socket = setup_server_v4(0);
  port = get_local_port(sample_socket);
  close(sample_socket);
  port--; /* Next ephemeral port. */
  printf("Assuming we use the algo_bsd() port algorithm the next ephemeral \n"
	 "port should be port:%d\n", port);

  /* Bind a v4v6 socket to the next ephemeral port explicitly. */
  int server_socket_v4v6 = setup_server_v4v6(port);
  if (server_socket_v4v6 == -1) {
    return 1;
  }

  /* Bind to port 0 to get the next ephemeral port.*/
  int server_socket_v4 = setup_server_v4(0);
  if (server_socket_v4 == -1) {
    close(server_socket_v4v6);
    return 1;
  }

  /* Check if we actually got the same port. */
  if (get_local_port(server_socket_v4v6) == get_local_port(server_socket_v4))
    printf("We got the same port!\n");


  int client_socket = setup_client(port);
  if (client_socket == -1) {
    close(server_socket_v4v6);
    close(server_socket_v4);
    return 1;
  }

  char send_buf[] = "Hello World!";
  char receive_buf[1024] = { 0 };
  ssize_t ret = 0;

  ret = send(client_socket, send_buf, sizeof(send_buf), 0);

  if (ret == -1) {
    perror("send");
    goto END;
  }

  ret = recv(server_socket_v4v6, &receive_buf, sizeof(receive_buf), 0);
  if (ret == -1) {
    perror("recv");
    goto END;
  }

  printf("%s\n", receive_buf);

 END:
  close(client_socket);
  close(server_socket_v4v6);
  close(server_socket_v4);
  return 0;
}

>Fix:
When the port allocator allocates an IPv4 port it should ask in6_pcblookup_port() to see if it finds a port which does not have the IN6P_IPV6_V6ONLY flag set and bound to an unspecified port.

OR

When binding an IPv4/v6 socket in in6_pcb.c to and unspecified address it should also bind it in in_pcb.c

OR

Something else. I don't know the architecture of the IP stack.

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.43 2018/01/16 07:36:43 maya Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2017 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.