summaryrefslogtreecommitdiffstats
path: root/courgette/encoded_program_fuzz_unittest.cc
blob: 9801fc86b5088466b0fb916f2ae235f487e53b81 (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Fuzz testing for EncodedProgram serialized format and assembly.
//
// We would like some assurance that if an EncodedProgram is malformed we will
// not crash.  The EncodedProgram could be malformed either due to malicious
// attack to due to an error in patch generation.
//
// We try a lot of arbitrary modifications to the serialized form and make sure
// that the outcome is not a crash.

#include "base/test/test_suite.h"

#include "courgette/base_test_unittest.h"
#include "courgette/courgette.h"
#include "courgette/streams.h"

class DecodeFuzzTest : public BaseTest {
 public:
  void FuzzExe(const char *) const;

 private:
  void FuzzByte(const std::string& buffer, const std::string& output,
                size_t index) const;
  void FuzzBits(const std::string& buffer, const std::string& output,
                size_t index, int bits_to_flip) const;

  // Returns true if could assemble, false if rejected.
  bool TryAssemble(const std::string& buffer, std::string* output) const;
};

// Loads an executable and does fuzz testing in the serialized format.
void DecodeFuzzTest::FuzzExe(const char* file_name) const {
  std::string file1 = FileContents(file_name);

  const void* original_buffer = file1.c_str();
  size_t original_length = file1.length();

  courgette::AssemblyProgram* program = NULL;
  const courgette::Status parse_status =
      courgette::ParseDetectedExecutable(original_buffer, original_length,
                                         &program);
  EXPECT_EQ(courgette::C_OK, parse_status);

  courgette::EncodedProgram* encoded = NULL;

  const courgette::Status encode_status = Encode(program, &encoded);
  EXPECT_EQ(courgette::C_OK, encode_status);

  DeleteAssemblyProgram(program);

  courgette::SinkStreamSet sinks;
  const courgette::Status write_status = WriteEncodedProgram(encoded, &sinks);
  EXPECT_EQ(courgette::C_OK, write_status);

  DeleteEncodedProgram(encoded);

  courgette::SinkStream sink;
  bool can_collect = sinks.CopyTo(&sink);
  EXPECT_TRUE(can_collect);

  size_t length = sink.Length();

  std::string base_buffer(reinterpret_cast<const char*>(sink.Buffer()), length);
  std::string base_output;
  bool ok = TryAssemble(base_buffer, &base_output);
  EXPECT_TRUE(ok);

  // Now we have a good serialized EncodedProgram in |base_buffer|. Time to
  // fuzz.

  // More intense fuzzing on the first part because it contains more control
  // information like substeam lengths.
  size_t position = 0;
  for ( ;  position < 100 && position < length;  position += 1) {
    FuzzByte(base_buffer, base_output, position);
  }
  // We would love to fuzz every position, but it takes too long.
  for ( ;  position < length;  position += 900) {
    FuzzByte(base_buffer, base_output, position);
  }
}

// FuzzByte tries to break the EncodedProgram deserializer and assembler.  It
// takes a good serialization of and EncodedProgram, flips some bits, and checks
// that the behaviour is reasonable.  It has testing checks for unreasonable
// behaviours.
void DecodeFuzzTest::FuzzByte(const std::string& base_buffer,
                              const std::string& base_output,
                              size_t index) const {
  printf("Fuzzing position %d\n", static_cast<int>(index));

  // The following 10 values are a compromize between run time and coverage of
  // the 255 'wrong' values at this byte position.

  // 0xFF flips all the bits.
  FuzzBits(base_buffer, base_output, index, 0xFF);
  // 0x7F flips the most bits without changing Varint32 framing.
  FuzzBits(base_buffer, base_output, index, 0x7F);
  // These all flip one bit.
  FuzzBits(base_buffer, base_output, index, 0x80);
  FuzzBits(base_buffer, base_output, index, 0x40);
  FuzzBits(base_buffer, base_output, index, 0x20);
  FuzzBits(base_buffer, base_output, index, 0x10);
  FuzzBits(base_buffer, base_output, index, 0x08);
  FuzzBits(base_buffer, base_output, index, 0x04);
  FuzzBits(base_buffer, base_output, index, 0x02);
  FuzzBits(base_buffer, base_output, index, 0x01);
}

// FuzzBits tries to break the EncodedProgram deserializer and assembler.  It
// takes a good serialization of and EncodedProgram, flips some bits, and checks
// that the behaviour is reasonable.
//
// There are EXPECT calls to check for unreasonable behaviour.  These are
// somewhat arbitrary in that the parameters cannot easily be derived from first
// principles.  They may need updating as the serialized format evolves.
void DecodeFuzzTest::FuzzBits(const std::string& base_buffer,
                              const std::string& base_output,
                              size_t index, int bits_to_flip) const {
  std::string modified_buffer = base_buffer;
  std::string modified_output;
  modified_buffer[index] ^= bits_to_flip;

  bool ok = TryAssemble(modified_buffer, &modified_output);

  if (ok) {
    // We normally expect TryAssemble to fail.  But sometimes it succeeds.
    // What could have happened?  We changed one byte in the serialized form:
    //
    //  * If we changed one of the copied bytes, we would see a single byte
    //    change in the output.
    //  * If we changed an address table element, all the references to that
    //    address would be different.
    //  * If we changed a copy count, we would run out of data in some stream,
    //    or leave data remaining, so should not be here.
    //  * If we changed an origin address, it could affect all relocations based
    //    off that address.  If no relocations were based off the address then
    //    there will be no changes.
    //  * If we changed an origin address, it could cause some abs32 relocs to
    //    shift from one page to the next, changing the number and layout of
    //    blocks in the base relocation table.

    // Generated length could vary slightly due to base relocation table layout.
    // In the worst case the number of base relocation blocks doubles, approx
    // 12/4096 or 0.3% size of file.
    size_t base_length = base_output.length();
    size_t modified_length = modified_output.length();
    ptrdiff_t diff = base_length - modified_length;
    if (diff < -200 || diff > 200) {
      EXPECT_EQ(base_length, modified_length);
    }

    size_t changed_byte_count = 0;
    for (size_t i = 0;  i < base_length && i < modified_length; ++i) {
      changed_byte_count += (base_output[i] != modified_output[i]);
    }

    if (index > 60) {                     // Beyond the origin addresses ...
      EXPECT_NE(0U, changed_byte_count);   //   ... we expect some difference.
    }
    // Currently all changes are smaller than this number:
    EXPECT_GE(45000U, changed_byte_count);
  }
}

bool DecodeFuzzTest::TryAssemble(const std::string& buffer,
                                 std::string* output) const {
  courgette::EncodedProgram *encoded = NULL;
  bool result = false;

  courgette::SourceStreamSet sources;
  bool can_get_source_streams = sources.Init(buffer.c_str(), buffer.length());
  if (can_get_source_streams) {
    const courgette::Status read_status =
        ReadEncodedProgram(&sources, &encoded);
    if (read_status == courgette::C_OK) {
      courgette::SinkStream assembled;
      const courgette::Status assemble_status = Assemble(encoded, &assembled);

      if (assemble_status == courgette::C_OK) {
        const void* assembled_buffer = assembled.Buffer();
        size_t assembled_length = assembled.Length();

        output->clear();
        output->assign(reinterpret_cast<const char*>(assembled_buffer),
                       assembled_length);
        result = true;
      }
    }
  }

  DeleteEncodedProgram(encoded);

  return result;
}

TEST_F(DecodeFuzzTest, All) {
  FuzzExe("setup1.exe");
  FuzzExe("elf-32-1.exe");
}

int main(int argc, char** argv) {
  return base::TestSuite(argc, argv).Run();
}