summaryrefslogtreecommitdiff
path: root/test/cctest/test_crypto_clienthello.cc
blob: 870857cf906109cfa4a2240844f4cd62da798f14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include "crypto/crypto_clienthello-inl.h"
#include "gtest/gtest.h"

// If the test is being compiled with an address sanitizer enabled, it should
// catch the memory violation, so do not use a guard page.
#ifdef __SANITIZE_ADDRESS__
#define NO_GUARD_PAGE
#elif defined(__has_feature)
#if __has_feature(address_sanitizer)
#define NO_GUARD_PAGE
#endif
#endif

// If the test is running without an address sanitizer, see if we can use
// mprotect() or VirtualProtect() to cause a segmentation fault when spatial
// safety is violated.
#if !defined(NO_GUARD_PAGE)
#ifdef __linux__
#include <sys/mman.h>
#include <unistd.h>
#if defined(_SC_PAGE_SIZE) && defined(PROT_NONE) && defined(PROT_READ) &&      \
    defined(PROT_WRITE)
#define USE_MPROTECT
#endif
#elif defined(_WIN32) && defined(_MSC_VER)
#include <Windows.h>
#include <memoryapi.h>
#define USE_VIRTUALPROTECT
#endif
#endif

#if defined(USE_MPROTECT)
size_t GetPageSize() {
  int page_size = sysconf(_SC_PAGE_SIZE);
  CHECK_GE(page_size, 1);
  return page_size;
}
#elif defined(USE_VIRTUALPROTECT)
size_t GetPageSize() {
  SYSTEM_INFO system_info;
  GetSystemInfo(&system_info);
  return system_info.dwPageSize;
}
#endif

template <size_t N>
class OverrunGuardedBuffer {
 public:
  OverrunGuardedBuffer() {
#if defined(USE_MPROTECT) || defined(USE_VIRTUALPROTECT)
    size_t page = GetPageSize();
    CHECK_GE(page, N);
#endif
#ifdef USE_MPROTECT
    // Place the packet right before a guard page, which, when accessed, causes
    // a segmentation fault.
    alloc_base = static_cast<uint8_t*>(aligned_alloc(page, 2 * page));
    CHECK_NOT_NULL(alloc_base);
    uint8_t* second_page = alloc_base + page;
    CHECK_EQ(mprotect(second_page, page, PROT_NONE), 0);
    data_base = second_page - N;
#elif defined(USE_VIRTUALPROTECT)
    // On Windows, it works almost the same way.
    alloc_base = static_cast<uint8_t*>(
        VirtualAlloc(nullptr, 2 * page, MEM_COMMIT, PAGE_READWRITE));
    CHECK_NOT_NULL(alloc_base);
    uint8_t* second_page = alloc_base + page;
    DWORD old_prot;
    CHECK_NE(VirtualProtect(second_page, page, PAGE_NOACCESS, &old_prot), 0);
    CHECK_EQ(old_prot, PAGE_READWRITE);
    data_base = second_page - N;
#else
    // Place the packet in a regular allocated buffer. The bug causes undefined
    // behavior, which might crash the process, and when it does not, address
    // sanitizers and valgrind will catch it.
    alloc_base = static_cast<uint8_t*>(malloc(N));
    CHECK_NOT_NULL(alloc_base);
    data_base = alloc_base;
#endif
  }

  OverrunGuardedBuffer(const OverrunGuardedBuffer& other) = delete;
  OverrunGuardedBuffer& operator=(const OverrunGuardedBuffer& other) = delete;

  ~OverrunGuardedBuffer() {
#if defined(USE_MPROTECT) || defined(USE_VIRTUALPROTECT)
    size_t page = GetPageSize();
#endif
#ifdef USE_VIRTUALPROTECT
    VirtualFree(alloc_base, 2 * page, MEM_RELEASE);
#else
#ifdef USE_MPROTECT
    // Revert page protection such that the memory can be free()'d.
    uint8_t* second_page = alloc_base + page;
    CHECK_EQ(mprotect(second_page, page, PROT_READ | PROT_WRITE), 0);
#endif
    free(alloc_base);
#endif
  }

  uint8_t* data() {
    return data_base;
  }

 private:
  uint8_t* alloc_base;
  uint8_t* data_base;
};

// Test that ClientHelloParser::ParseHeader() does not blindly trust the client
// to send a valid frame length and subsequently does not read out-of-bounds.
TEST(NodeCrypto, ClientHelloParserParseHeaderOutOfBoundsRead) {
  using node::crypto::ClientHelloParser;

  // This is the simplest packet triggering the bug.
  const uint8_t packet[] = {0x16, 0x03, 0x01, 0x00, 0x00};
  OverrunGuardedBuffer<sizeof(packet)> buffer;
  memcpy(buffer.data(), packet, sizeof(packet));

  // Let the ClientHelloParser parse the packet. This should not lead to a
  // segmentation fault or to undefined behavior.
  node::crypto::ClientHelloParser parser;
  bool end_cb_called = false;
  parser.Start([](void* arg, auto hello) { GTEST_FAIL(); },
               [](void* arg) {
                 bool* end_cb_called = static_cast<bool*>(arg);
                 EXPECT_FALSE(*end_cb_called);
                 *end_cb_called = true;
               },
               &end_cb_called);
  parser.Parse(buffer.data(), sizeof(packet));
  EXPECT_TRUE(end_cb_called);
}