• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 Google LLC
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 <array>
16 #include <cstdint>
17 #include <random>
18 #include <span>
19 #include <utility>
20 #include <vector>
21 
22 #include "fuzztest/domain_core.h"
23 #include "fuzztest/fuzztest.h"
24 #include "gtest/gtest.h"
25 #include "nearby_protocol.h"
26 #include "np_cpp_ffi_types.h"
27 #include "shared_test_util.h"
28 
29 // redefine test data as std::vector types since fuzztest does not support
30 // template class use in its input domains (ie: std::array<T, N>), and we want
31 // to use our test data to seed the fuzzer
32 static std::vector<uint8_t> V0AdvEmptyVec(V0AdvEmptyBytes.begin(),
33                                           V0AdvEmptyBytes.end());
34 static std::vector<uint8_t> V1AdvEmptyVec(V1AdvEmptyBytes.begin(),
35                                           V1AdvEmptyBytes.end());
36 static std::vector<uint8_t> V0AdvPlaintextVec(V0AdvPlaintextBytes.begin(),
37                                               V0AdvPlaintextBytes.end());
38 static std::vector<uint8_t> V0AdvPlaintextMultiDeVec(
39     V0AdvPlaintextMultiDeBytes.begin(), V0AdvPlaintextMultiDeBytes.end());
40 static std::vector<uint8_t> V1AdvPlaintextVec(V1AdvPlaintextBytes.begin(),
41                                               V1AdvPlaintextBytes.end());
42 static std::vector<uint8_t> V0AdvEncryptedVec(V0AdvEncryptedBytes.begin(),
43                                               V0AdvEncryptedBytes.end());
44 static std::vector<uint8_t> V1AdvEncryptedVec(V1AdvEncryptedBytes.begin(),
45                                               V1AdvEncryptedBytes.end());
46 
47 void HandleAdvertisementResult(nearby_protocol::DeserializeAdvertisementResult);
48 
PlaintextDeserializer(std::span<const uint8_t> adv_bytes)49 void PlaintextDeserializer(std::span<const uint8_t> adv_bytes) {
50   nearby_protocol::CredentialSlab slab;
51   nearby_protocol::CredentialBook book(slab);
52   auto buffer = nearby_protocol::ByteBuffer<255>::TryFromSpan(adv_bytes);
53   EXPECT_TRUE(buffer.ok());
54 
55   nearby_protocol::RawAdvertisementPayload payload(
56       (nearby_protocol::ByteBuffer<255>(*buffer)));
57   auto deserialize_result =
58       nearby_protocol::Deserializer::DeserializeAdvertisement(payload, book);
59 
60   // Since we are seeding with valid data, we can add extra calls into the
61   // result processing APIs to ensure none of the internal asserts are
62   // triggered.
63   HandleAdvertisementResult(std::move(deserialize_result));
64 }
65 
66 FUZZ_TEST(NpCppDeserializationFuzzers, PlaintextDeserializer)
67     .WithDomains(fuzztest::Arbitrary<std::vector<uint8_t>>()
68                      .WithMinSize(0)
69                      .WithMaxSize(255))
70     .WithSeeds({V0AdvEmptyVec, V1AdvEmptyVec, V0AdvPlaintextVec,
71                 V0AdvPlaintextMultiDeVec, V1AdvPlaintextVec, V0AdvEncryptedVec,
72                 V1AdvEncryptedVec});
73 
74 // The data which is automatically generated by the fuzzer
75 struct IdentityData {
76   uint32_t credential_id;
77   std::array<uint8_t, 32> key_seed;
78   std::array<uint8_t, 32> legacy_metadata_key_hmac;
79   std::array<uint8_t, 32> expected_unsigned_identity_token_hmac;
80   std::array<uint8_t, 32> expected_signed_identity_token_hmac;
81   std::array<uint8_t, 32> pub_key;
82   std::vector<uint8_t> encrypted_metadata_bytes;
83 };
84 
85 static struct IdentityData V0TestCaseIdentityData {
86   .credential_id = static_cast<uint32_t>(rand()), .key_seed = V0AdvKeySeed,
87   .legacy_metadata_key_hmac = V0AdvLegacyIdentityTokenHmac,
88   .encrypted_metadata_bytes = V0AdvEncryptedMetadata
89 };
90 
91 static struct IdentityData V1TestCaseIdentityData {
92   .credential_id = static_cast<uint32_t>(rand()), .key_seed = V1AdvKeySeed,
93   .expected_unsigned_identity_token_hmac =
94       V1AdvExpectedMicExtendedSaltIdentityTokenHmac,
95   .expected_signed_identity_token_hmac =
96       V1AdvExpectedSignatureIdentityTokenHmac,
97   .pub_key = V1AdvPublicKey, .encrypted_metadata_bytes = V1AdvEncryptedMetadata,
98 };
99 
100 // Now lets try feeding the fuzzer some credential data that can successfully
101 // decrypt advertisements to improve its efficiency, and provide extra coverage
102 // on on credential/iteration code paths.
103 // TODO: Add more interesting credential seed data once we have C++
104 // serialization APIs, ie multiple sections with multiple valid credentials
105 // and combinations of matching and undecryptable sections etc.
DeserializeWithCredentials(std::span<const IdentityData> identities,std::span<const uint8_t> adv_bytes)106 void DeserializeWithCredentials(std::span<const IdentityData> identities,
107                                 std::span<const uint8_t> adv_bytes) {
108   nearby_protocol::CredentialSlab slab;
109   // populate book with fuzzer generated credential data
110   for (auto data : identities) {
111     nearby_protocol::MatchedCredentialData match_data(
112         123, data.encrypted_metadata_bytes);
113     nearby_protocol::V0MatchableCredential v0_cred(
114         data.key_seed, data.legacy_metadata_key_hmac, match_data);
115     // adding v0 credentials is infallible
116     slab.AddV0Credential(v0_cred);
117 
118     nearby_protocol::V1MatchableCredential v1_cred(
119         data.key_seed, data.expected_unsigned_identity_token_hmac,
120         data.expected_signed_identity_token_hmac, data.pub_key, match_data);
121     [[maybe_unused]] auto result = slab.AddV1Credential(v1_cred);
122   }
123 
124   nearby_protocol::CredentialBook book(slab);
125   auto buffer = nearby_protocol::ByteBuffer<255>::TryFromSpan(adv_bytes);
126   EXPECT_TRUE(buffer.ok());
127 
128   nearby_protocol::RawAdvertisementPayload payload(
129       (nearby_protocol::ByteBuffer<255>(*buffer)));
130   auto deserialize_result =
131       nearby_protocol::Deserializer::DeserializeAdvertisement(payload, book);
132 
133   // Since we are seeding with valid data, we can add extra calls into the
134   // result processing APIs to ensure none of the internal asserts are
135   // triggered.
136   HandleAdvertisementResult(std::move(deserialize_result));
137 }
138 
139 std::vector<std::tuple<std::vector<IdentityData>, std::vector<uint8_t>>>
DeserializeWithCredentialSeedData()140 DeserializeWithCredentialSeedData() {
141   return {
142       {{{V0TestCaseIdentityData, V1TestCaseIdentityData}, V0AdvEncryptedVec},
143        {{V0TestCaseIdentityData, V1TestCaseIdentityData}, V1AdvEncryptedVec}},
144   };
145 }
146 
TEST(NpCppDeserializationFuzzers,InvalidPublicKeyInCredential)147 TEST(NpCppDeserializationFuzzers, InvalidPublicKeyInCredential) {
148   std::vector<IdentityData> identities;
149   identities.push_back(
150       {1804289383,
151        {17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
152         17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17},
153        {136, 51,  222, 213, 77,  0,   146, 232, 128, 112, 213,
154         31,  24,  236, 34,  69,  117, 124, 36,  223, 227, 140,
155         178, 222, 119, 182, 120, 133, 252, 165, 103, 77},
156        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
157         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
158        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
160        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
162        fuzztest::ToByteArray("")});
163   identities.push_back({846930886,
164                         {49, 67, 99, 30,  202, 232, 151, 75,  150, 80,  204,
165                          28, 72, 37, 14,  129, 88,  6,   129, 81,  249, 235,
166                          37, 35, 3,  212, 151, 109, 149, 25,  145, 57},
167                         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
168                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
169                         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
170                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
171                         {28,  188, 235, 220, 23,  181, 145, 229, 7,   157, 112,
172                          193, 232, 75,  204, 219, 75,  15,  118, 131, 89,  98,
173                          10,  45,  85,  11,  59,  54,  164, 146, 139, 19},
174                         {109, 13,  182, 9,   16,  177, 83,  196, 126, 16,  22,
175                          20,  156, 159, 242, 20,  15,  236, 83,  118, 227, 7,
176                          217, 211, 158, 174, 231, 69,  44,  3,   236, 109},
177                         fuzztest::ToByteArray("")});
178 
179   DeserializeWithCredentials(
180       identities,
181       fuzztest::ToByteArray(
182           " g\221\020\010\255iF\004]"
183           "\256m\267\367\\\323\270\254\360\277u\220\001\276s3\244v\204J\t"
184           "\017+"
185           "\231G\337\213F\312\026\316\023\265nS\256("
186           "VD\016\246\215\353\241\021\257N\033\340\216\365\272\220O."
187           "\224\374\336\246\177]"
188           "\3107\265\357\312\254\213\237\033\324\306\021\205\323g92\321\202"
189           "\312"
190           "N\271F\003\203hV\013\314\375z*\276\007"));
191 }
192 
TEST(NpCppDeserializationFuzzers,PlaintextDeserializerRegression)193 TEST(NpCppDeserializationFuzzers, PlaintextDeserializerRegression) {
194   PlaintextDeserializer(fuzztest::ToByteArray(
195       " 4\221\020\005\255iF\004]"
196       "\256\275m\267\367\\\270\254\360\277u\220\001G\337\213F\312\026\316\023"
197       "\265nS\256(VD\016\243\215\353\241\021\257N\020\340\216\365\272\220O."
198       "\224\374\336\246\177]"
199       "\3107\267\357\312\254\213\237\033\324\306\021\205\323g92\321\202\312N"
200       "\271D\003\203hV\013\314\375z*\276\007"));
201 }
202 
203 FUZZ_TEST(NpCppDeserializationFuzzers, DeserializeWithCredentials)
204     .WithDomains(fuzztest::Arbitrary<std::vector<IdentityData>>()
205                      .WithMinSize(0)
206                      .WithMaxSize(1000),
207                  fuzztest::Arbitrary<std::vector<uint8_t>>()
208                      .WithMinSize(0)
209                      .WithMaxSize(255))
210     .WithSeeds(DeserializeWithCredentialSeedData);
211 
212 // Lets encourage the fuzzer to try with a lot of credentials and make sure
213 // nothing falls apart. By default the vec ranges are somewhere between 1-20
214 // even with setting a max of 1000, so setting a high minimum here to ensure the
215 // higher end of credential iteration is hit.
216 FUZZ_TEST(NpCppDeserializationFuzzersLotsOfCredentials,
217           DeserializeWithCredentials)
218     .WithDomains(
219         fuzztest::Arbitrary<std::vector<IdentityData>>().WithMinSize(10000),
220         fuzztest::Arbitrary<std::vector<uint8_t>>().WithMinSize(0).WithMaxSize(
221             255));
222 
223 // Helpers to trigger result processing code paths.
224 void HandleV0Adv(nearby_protocol::DeserializedV0Advertisement);
225 void HandleLegibleV0Adv(nearby_protocol::LegibleDeserializedV0Advertisement);
226 void HandleV0IdentityKind(nearby_protocol::DeserializedV0IdentityKind);
227 void HandleDataElement(nearby_protocol::V0DataElement);
228 
229 void HandleV1Adv(nearby_protocol::DeserializedV1Advertisement);
230 void HandleV1Section(nearby_protocol::DeserializedV1Section);
231 void HandleV1DataElement(nearby_protocol::V1DataElement);
232 
HandleAdvertisementResult(nearby_protocol::DeserializeAdvertisementResult result)233 void HandleAdvertisementResult(
234     nearby_protocol::DeserializeAdvertisementResult result) {
235   switch (result.GetKind()) {
236     case nearby_protocol::DeserializeAdvertisementResultKind::Error:
237       break;
238     case nearby_protocol::DeserializeAdvertisementResultKind::V0:
239       HandleV0Adv(result.IntoV0());
240       break;
241     case nearby_protocol::DeserializeAdvertisementResultKind::V1:
242       HandleV1Adv(result.IntoV1());
243       break;
244   }
245 }
246 
HandleV0Adv(nearby_protocol::DeserializedV0Advertisement result)247 void HandleV0Adv(nearby_protocol::DeserializedV0Advertisement result) {
248   switch (result.GetKind()) {
249     case nearby_protocol::DeserializedV0AdvertisementKind::Legible:
250       HandleLegibleV0Adv(result.IntoLegible());
251       break;
252     case nearby_protocol::DeserializedV0AdvertisementKind::
253         NoMatchingCredentials:
254       break;
255   }
256 }
257 
HandleLegibleV0Adv(nearby_protocol::LegibleDeserializedV0Advertisement legible_adv)258 void HandleLegibleV0Adv(
259     nearby_protocol::LegibleDeserializedV0Advertisement legible_adv) {
260   auto num_des = legible_adv.GetNumberOfDataElements();
261   auto payload = legible_adv.IntoPayload();
262   for (int i = 0; i < num_des; i++) {
263     auto de_result = payload.TryGetDataElement(i);
264     if (!de_result.ok()) {
265       return;
266     }
267     HandleDataElement(de_result.value());
268   }
269 }
270 
HandleDataElement(nearby_protocol::V0DataElement de)271 void HandleDataElement(nearby_protocol::V0DataElement de) {
272   switch (de.GetKind()) {
273     case nearby_protocol::V0DataElementKind::TxPower: {
274       [[maybe_unused]] auto tx_power = de.AsTxPower();
275       break;
276     }
277     case nearby_protocol::V0DataElementKind::Actions: {
278       [[maybe_unused]] auto actions = de.AsActions();
279       break;
280     }
281   }
282 }
283 
HandleV1Adv(nearby_protocol::DeserializedV1Advertisement adv)284 void HandleV1Adv(nearby_protocol::DeserializedV1Advertisement adv) {
285   auto legible_sections = adv.GetNumLegibleSections();
286   [[maybe_unused]] auto encrypted_sections = adv.GetNumUndecryptableSections();
287   for (auto i = 0; i < legible_sections; i++) {
288     auto section_result = adv.TryGetSection(i);
289     if (!section_result.ok()) {
290       return;
291     }
292     HandleV1Section(section_result.value());
293   }
294 }
295 
HandleV1Section(nearby_protocol::DeserializedV1Section section)296 void HandleV1Section(nearby_protocol::DeserializedV1Section section) {
297   auto num_des = section.NumberOfDataElements();
298   for (auto i = 0; i < num_des; i++) {
299     auto de_result = section.TryGetDataElement(i);
300     if (!de_result.ok()) {
301       return;
302     }
303   }
304 }
305