gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/ip6tables.cc (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <linux/capability.h>
    16  #include <sys/socket.h>
    17  
    18  #include <cstring>
    19  
    20  #include "gtest/gtest.h"
    21  #include "test/syscalls/linux/iptables.h"
    22  #include "test/util/capability_util.h"
    23  #include "test/util/file_descriptor.h"
    24  #include "test/util/socket_util.h"
    25  #include "test/util/test_util.h"
    26  
    27  namespace gvisor {
    28  namespace testing {
    29  
    30  namespace {
    31  
    32  constexpr char kNatTablename[] = "nat";
    33  constexpr char kErrorTarget[] = "ERROR";
    34  constexpr size_t kEmptyStandardEntrySize =
    35      sizeof(struct ip6t_entry) + sizeof(struct xt_standard_target);
    36  constexpr size_t kEmptyErrorEntrySize =
    37      sizeof(struct ip6t_entry) + sizeof(struct xt_error_target);
    38  
    39  TEST(IP6TablesBasic, FailSockoptNonRaw) {
    40    // Even if the user has CAP_NET_RAW, they shouldn't be able to use the
    41    // ip6tables sockopts with a non-raw socket.
    42    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
    43  
    44    int sock;
    45    ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds());
    46  
    47    struct ipt_getinfo info = {};
    48    snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
    49    socklen_t info_size = sizeof(info);
    50    EXPECT_THAT(getsockopt(sock, SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
    51                SyscallFailsWithErrno(ENOPROTOOPT));
    52  
    53    EXPECT_THAT(close(sock), SyscallSucceeds());
    54  }
    55  
    56  TEST(IP6TablesBasic, GetInfoErrorPrecedence) {
    57    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
    58  
    59    int sock;
    60    ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds());
    61  
    62    // When using the wrong type of socket and a too-short optlen, we should get
    63    // EINVAL.
    64    struct ipt_getinfo info = {};
    65    snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
    66    socklen_t info_size = sizeof(info) - 1;
    67    EXPECT_THAT(getsockopt(sock, SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
    68                SyscallFailsWithErrno(EINVAL));
    69  }
    70  
    71  TEST(IP6TablesBasic, GetEntriesErrorPrecedence) {
    72    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
    73  
    74    int sock;
    75    ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds());
    76  
    77    // When using the wrong type of socket and a too-short optlen, we should get
    78    // EINVAL.
    79    struct ip6t_get_entries entries = {};
    80    socklen_t entries_size = sizeof(struct ip6t_get_entries) - 1;
    81    snprintf(entries.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
    82    EXPECT_THAT(
    83        getsockopt(sock, SOL_IPV6, IP6T_SO_GET_ENTRIES, &entries, &entries_size),
    84        SyscallFailsWithErrno(EINVAL));
    85  }
    86  
    87  TEST(IP6TablesBasic, GetRevision) {
    88    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
    89  
    90    int sock;
    91    ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW),
    92                SyscallSucceeds());
    93  
    94    struct xt_get_revision rev = {};
    95    socklen_t rev_len = sizeof(rev);
    96  
    97    snprintf(rev.name, sizeof(rev.name), "REDIRECT");
    98    rev.revision = 0;
    99  
   100    // Revision 0 exists.
   101    EXPECT_THAT(
   102        getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len),
   103        SyscallSucceeds());
   104    EXPECT_EQ(rev.revision, 0);
   105  
   106    // Revisions > 0 don't exist.
   107    rev.revision = 1;
   108    EXPECT_THAT(
   109        getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len),
   110        SyscallFailsWithErrno(EPROTONOSUPPORT));
   111  }
   112  
   113  // This tests the initial state of a machine with empty ip6tables via
   114  // getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are
   115  // empty when running in native, but we can test that gVisor has the same
   116  // initial state that a newly-booted Linux machine would have.
   117  TEST(IP6TablesTest, InitialInfo) {
   118    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
   119  
   120    FileDescriptor sock =
   121        ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW));
   122  
   123    // Get info via sockopt.
   124    struct ipt_getinfo info = {};
   125    snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
   126    socklen_t info_size = sizeof(info);
   127    ASSERT_THAT(
   128        getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
   129        SyscallSucceeds());
   130  
   131    // The nat table supports PREROUTING, and OUTPUT.
   132    unsigned int valid_hooks =
   133        (1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_OUT) |
   134        (1 << NF_IP6_POST_ROUTING) | (1 << NF_IP6_LOCAL_IN);
   135    EXPECT_EQ(info.valid_hooks, valid_hooks);
   136  
   137    // Each chain consists of an empty entry with a standard target..
   138    EXPECT_EQ(info.hook_entry[NF_IP6_PRE_ROUTING], 0);
   139    EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize);
   140    EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2);
   141    EXPECT_EQ(info.hook_entry[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3);
   142  
   143    // The underflow points are the same as the entry points.
   144    EXPECT_EQ(info.underflow[NF_IP6_PRE_ROUTING], 0);
   145    EXPECT_EQ(info.underflow[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize);
   146    EXPECT_EQ(info.underflow[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2);
   147    EXPECT_EQ(info.underflow[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3);
   148  
   149    // One entry for each chain, plus an error entry at the end.
   150    EXPECT_EQ(info.num_entries, 5);
   151  
   152    EXPECT_EQ(info.size, 4 * kEmptyStandardEntrySize + kEmptyErrorEntrySize);
   153    EXPECT_EQ(strcmp(info.name, kNatTablename), 0);
   154  }
   155  
   156  // This tests the initial state of a machine with empty ip6tables via
   157  // getsockopt(IP6T_SO_GET_ENTRIES). We don't have a guarantee that the iptables
   158  // are empty when running in native, but we can test that gVisor has the same
   159  // initial state that a newly-booted Linux machine would have.
   160  TEST(IP6TablesTest, InitialEntries) {
   161    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
   162  
   163    FileDescriptor sock =
   164        ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW));
   165  
   166    // Get info via sockopt.
   167    struct ipt_getinfo info = {};
   168    snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
   169    socklen_t info_size = sizeof(info);
   170    ASSERT_THAT(
   171        getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
   172        SyscallSucceeds());
   173  
   174    // Use info to get entries.
   175    socklen_t entries_size = sizeof(struct ip6t_get_entries) + info.size;
   176    struct ip6t_get_entries* entries =
   177        static_cast<struct ip6t_get_entries*>(malloc(entries_size));
   178    snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
   179    entries->size = info.size;
   180    ASSERT_THAT(getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_ENTRIES, entries,
   181                           &entries_size),
   182                SyscallSucceeds());
   183  
   184    // Verify the name and size.
   185    ASSERT_EQ(info.size, entries->size);
   186    ASSERT_EQ(strcmp(entries->name, kNatTablename), 0);
   187  
   188    // Verify that the entrytable is 4 entries with accept targets and no matches
   189    // followed by a single error target.
   190    size_t entry_offset = 0;
   191    while (entry_offset < entries->size) {
   192      struct ip6t_entry* entry = reinterpret_cast<struct ip6t_entry*>(
   193          reinterpret_cast<char*>(entries->entrytable) + entry_offset);
   194  
   195      // ipv6 should be zeroed.
   196      struct ip6t_ip6 zeroed;
   197      memset(&zeroed, 0, sizeof(zeroed));
   198      ASSERT_EQ(memcmp(static_cast<void*>(&zeroed),
   199                       static_cast<void*>(&entry->ipv6), sizeof(zeroed)),
   200                0);
   201  
   202      // target_offset should be zero.
   203      EXPECT_EQ(entry->target_offset, sizeof(ip6t_entry));
   204  
   205      if (entry_offset < kEmptyStandardEntrySize * 4) {
   206        // The first 4 entries are standard targets
   207        struct xt_standard_target* target =
   208            reinterpret_cast<struct xt_standard_target*>(entry->elems);
   209        EXPECT_EQ(entry->next_offset, kEmptyStandardEntrySize);
   210        EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
   211        EXPECT_EQ(strcmp(target->target.u.user.name, ""), 0);
   212        EXPECT_EQ(target->target.u.user.revision, 0);
   213        // This is what's returned for an accept verdict. I don't know why.
   214        EXPECT_EQ(target->verdict, -NF_ACCEPT - 1);
   215      } else {
   216        // The last entry is an error target
   217        struct xt_error_target* target =
   218            reinterpret_cast<struct xt_error_target*>(entry->elems);
   219        EXPECT_EQ(entry->next_offset, kEmptyErrorEntrySize);
   220        EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
   221        EXPECT_EQ(strcmp(target->target.u.user.name, kErrorTarget), 0);
   222        EXPECT_EQ(target->target.u.user.revision, 0);
   223        EXPECT_EQ(strcmp(target->errorname, kErrorTarget), 0);
   224      }
   225  
   226      entry_offset += entry->next_offset;
   227      break;
   228    }
   229  
   230    free(entries);
   231  }
   232  
   233  }  // namespace
   234  
   235  }  // namespace testing
   236  }  // namespace gvisor