• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / thomas.goyne_396

06 Jun 2024 03:48PM UTC coverage: 90.956% (+0.1%) from 90.858%
thomas.goyne_396

Pull #7698

Evergreen

tgoyne
Fix UB in Tokenizer
Pull Request #7698: RCORE-2141 RCORE-2142 Clean up a bunch of old encryption cruft

101814 of 180058 branches covered (56.55%)

1048 of 1098 new or added lines in 27 files covered. (95.45%)

132 existing lines in 20 files now uncovered.

214536 of 235869 relevant lines covered (90.96%)

6051586.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.52
/test/test_encrypted_file_mapping.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include "testsettings.hpp"
20

21
#if defined(TEST_ENCRYPTED_FILE_MAPPING)
22

23
#include <realm.hpp>
24
#include <realm/util/aes_cryptor.hpp>
25
#include <realm/util/encrypted_file_mapping.hpp>
26
#include <realm/util/file.hpp>
27

28
#include "test.hpp"
29

30
// Test independence and thread-safety
31
// -----------------------------------
32
//
33
// All tests must be thread safe and independent of each other. This
34
// is required because it allows for both shuffling of the execution
35
// order and for parallelized testing.
36
//
37
// In particular, avoid using std::rand() since it is not guaranteed
38
// to be thread safe. Instead use the API offered in
39
// `test/util/random.hpp`.
40
//
41
// All files created in tests must use the TEST_PATH macro (or one of
42
// its friends) to obtain a suitable file system path. See
43
// `test/util/test_path.hpp`.
44
//
45
//
46
// Debugging and the ONLY() macro
47
// ------------------------------
48
//
49
// A simple way of disabling all tests except one called `Foo`, is to
50
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
51
// test suite. Note that you can also use filtering by setting the
52
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
53
// this.
54
//
55
// Another way to debug a particular test, is to copy that test into
56
// `experiments/testcase.cpp` and then run `sh build.sh
57
// check-testcase` (or one of its friends) from the command line.
58

59
#if REALM_ENABLE_ENCRYPTION
60

61
using namespace realm;
62
using namespace realm::util;
63
using realm::FileDesc;
64

65
namespace {
66
const char test_key[] = "1234567890123456789012345678901123456789012345678901234567890123";
67
}
68

69
TEST(EncryptedFile_CryptorBasic)
70
{
2✔
71
    TEST_PATH(path);
2✔
72

73
    AESCryptor cryptor(test_key);
2✔
74
    cryptor.set_data_size(16);
2✔
75
    const char data[4096] = "test data";
2✔
76
    char buffer[4096];
2✔
77

78
    File file(path, realm::util::File::mode_Write);
2✔
79
    cryptor.write(file.get_descriptor(), 0, data);
2✔
80
    cryptor.read(file.get_descriptor(), 0, buffer);
2✔
81
    CHECK(memcmp(buffer, data, strlen(data)) == 0);
2✔
82
}
2✔
83

84
TEST(EncryptedFile_CryptorRepeatedWrites)
85
{
2✔
86
    TEST_PATH(path);
2✔
87
    AESCryptor cryptor(test_key);
2✔
88
    cryptor.set_data_size(16);
2✔
89

90
    const char data[4096] = "test data";
2✔
91
    char raw_buffer_1[8192] = {0}, raw_buffer_2[8192] = {0};
2✔
92
    File file(path, realm::util::File::mode_Write);
2✔
93

94
    cryptor.write(file.get_descriptor(), 0, data);
2✔
95
    ssize_t actual_read_1 = file.read(0, raw_buffer_1, sizeof(raw_buffer_1));
2✔
96
    CHECK_EQUAL(actual_read_1, sizeof(raw_buffer_1));
2✔
97

98
    cryptor.write(file.get_descriptor(), 0, data);
2✔
99
    ssize_t actual_read_2 = file.read(0, raw_buffer_2, sizeof(raw_buffer_2));
2✔
100
    CHECK_EQUAL(actual_read_2, sizeof(raw_buffer_2));
2✔
101

102
    CHECK(memcmp(raw_buffer_1, raw_buffer_2, sizeof(raw_buffer_1)) != 0);
2✔
103
}
2✔
104

105
TEST(EncryptedFile_SeparateCryptors)
106
{
2✔
107
    TEST_PATH(path);
2✔
108

109
    const char data[4096] = "test data";
2✔
110
    char buffer[4096];
2✔
111

112
    File file(path, realm::util::File::mode_Write);
2✔
113
    {
2✔
114
        AESCryptor cryptor(test_key);
2✔
115
        cryptor.set_data_size(16);
2✔
116
        cryptor.write(file.get_descriptor(), 0, data);
2✔
117
    }
2✔
118
    {
2✔
119
        AESCryptor cryptor(test_key);
2✔
120
        cryptor.set_data_size(16);
2✔
121
        cryptor.read(file.get_descriptor(), 0, buffer);
2✔
122
    }
2✔
123

124
    CHECK(memcmp(buffer, data, strlen(data)) == 0);
2✔
125
}
2✔
126

127
TEST(EncryptedFile_InterruptedWrite)
128
{
2✔
129
    TEST_PATH(path);
2✔
130

131
    const char data[4096] = "test data";
2✔
132

133
    File file(path, realm::util::File::mode_Write);
2✔
134
    {
2✔
135
        AESCryptor cryptor(test_key);
2✔
136
        cryptor.set_data_size(16);
2✔
137
        cryptor.write(file.get_descriptor(), 0, data);
2✔
138
    }
2✔
139

140
    // Fake an interrupted write which updates the IV table but not the data
141
    char buffer[4096];
2✔
142
    size_t actual_pread = file.read(0, buffer, 64);
2✔
143
    CHECK_EQUAL(actual_pread, 64);
2✔
144

145
    memcpy(buffer + 32, buffer, 32);
2✔
146
    buffer[5]++; // first byte of "hmac1" field in iv table
2✔
147
    file.write(0, buffer, 64);
2✔
148

149
    {
2✔
150
        AESCryptor cryptor(test_key);
2✔
151
        cryptor.set_data_size(16);
2✔
152
        cryptor.read(file.get_descriptor(), 0, buffer);
2✔
153
        CHECK(memcmp(buffer, data, strlen(data)) == 0);
2✔
154
    }
2✔
155
}
2✔
156

157
TEST(EncryptedFile_IVRefreshing)
158
{
2✔
159
    constexpr size_t page_size = 4096;
2✔
160
    constexpr size_t pages_per_metadata_block = 64;
2✔
161

162
    // enough data to span two metadata blocks
163
    constexpr size_t page_count = pages_per_metadata_block * 2;
2✔
164
    constexpr File::SizeType data_size = page_size * page_count;
2✔
165
    char data[page_size];
2✔
166
    std::iota(std::begin(data), std::end(data), 0);
2✔
167

168
    TEST_PATH(path);
2✔
169
    File file(path, realm::util::File::mode_Write);
2✔
170
    const FileDesc fd = file.get_descriptor();
2✔
171

172
    AESCryptor cryptor(test_key);
2✔
173
    cryptor.set_data_size(data_size);
2✔
174
    for (File::SizeType i = 0; i < data_size; i += page_size) {
258✔
175
        cryptor.write(fd, i, data);
256✔
176
    }
256✔
177
    // The IVs for the pages we just wrote should obviously be up to date
178
    for (size_t i = 0; i < page_count; ++i) {
258✔
179
        CHECK_NOT(cryptor.refresh_iv(fd, i));
256✔
180
    }
256✔
181
    // and we should see the same ones after rereading them
182
    cryptor.invalidate_ivs();
2✔
183
    for (size_t i = 0; i < page_count; ++i) {
258✔
184
        CHECK_NOT(cryptor.refresh_iv(fd, i));
256✔
185
    }
256✔
186

187
    AESCryptor cryptor2(test_key);
2✔
188
    cryptor2.set_data_size(data_size);
2✔
189
    for (size_t i = 0; i < page_count; ++i) {
258✔
190
        // Each IV should be up to date immediately after reading the page
191
        cryptor2.read(fd, File::SizeType(i) * page_size, data);
256✔
192
        CHECK_NOT(cryptor2.refresh_iv(fd, i));
256✔
193
    }
256✔
194

195
    // Nothing's changed so rereading them should report no refresh needed
196
    cryptor2.invalidate_ivs();
2✔
197
    for (size_t i = 0; i < page_count; ++i) {
258✔
198
        CHECK_NOT(cryptor2.refresh_iv(fd, i));
256✔
199
    }
256✔
200

201
    // Modify all pages, invalidate, verify each page needs to be refreshed
202
    // Note that even though this isn't changing the plaintext it does update
203
    // the ciphertext each time
204
    for (File::SizeType i = 0; i < data_size; i += page_size) {
258✔
205
        cryptor.write(fd, i, data);
256✔
206
    }
256✔
207
    cryptor2.invalidate_ivs();
2✔
208
    for (size_t i = 0; i < page_count; ++i) {
258✔
209
        CHECK(cryptor2.refresh_iv(fd, i));
256✔
210
        // refresh_iv only returns true once per page per write
211
        CHECK_NOT(cryptor2.refresh_iv(fd, i));
256✔
212
    }
256✔
213

214
    // Modify all pages, verifying that a refresh is needed after each one
215
    for (size_t i = 0; i < page_count; ++i) {
258✔
216
        cryptor.write(fd, File::SizeType(i) * page_size, data);
256✔
217
        cryptor2.invalidate_ivs();
256✔
218
        CHECK(cryptor2.refresh_iv(fd, i));
256✔
219
        CHECK_NOT(cryptor2.refresh_iv(fd, i));
256✔
220
    }
256✔
221

222
    // Same thing but in reverse. This verifies that initialization of data
223
    // before the earliest populated point is tracked correctly
224
    for (size_t i = page_count; i > 0; --i) {
258✔
225
        cryptor.write(fd, File::SizeType(i - 1) * page_size, data);
256✔
226
        cryptor2.invalidate_ivs();
256✔
227
        CHECK(cryptor2.refresh_iv(fd, i - 1));
256✔
228
        CHECK_NOT(cryptor2.refresh_iv(fd, i - 1));
256✔
229
    }
256✔
230
}
2✔
231

232
TEST(EncryptedFile_NonPageAlignedMapping)
233
{
2✔
234
    TEST_PATH(path);
2✔
235
    {
2✔
236
        File f(path, File::mode_Write);
2✔
237
        f.set_encryption_key(test_util::crypt_key(true));
2✔
238
        f.resize(page_size() * 2);
2✔
239
        // Since no power-of-two page size is a multiple of 11, one of these
240
        // mapping will straddle a page
241
        for (size_t pos = 0; pos + 10 <= page_size() * 2; pos += 11) {
3,725✔
242
            File::Map<char> map(f, pos, File::access_ReadWrite, 10);
3,723✔
243
            util::encryption_read_barrier(map, 0, 10);
3,723✔
244
            for (int i = 0; i < 10; ++i)
40,953✔
245
                map.get_addr()[i] = char(i + 1);
37,230✔
246
            util::encryption_write_barrier(map, 0, 10);
3,723✔
247
        }
3,723✔
248
    }
2✔
249
    {
2✔
250
        File f(path, File::mode_Read);
2✔
251
        f.set_encryption_key(test_util::crypt_key(true));
2✔
252
        for (size_t pos = 0; pos + 17 <= page_size() * 2; pos += 7) {
5,849✔
253
            File::Map<char> map(f, pos, File::access_ReadOnly, 6);
5,847✔
254
            util::encryption_read_barrier(map, 0, 6);
5,847✔
255
            for (int i = 0; i < 6; ++i)
40,929✔
256
                CHECK_EQUAL(int(map.get_addr()[i]), (pos + i + 1) % 11);
35,082✔
257
        }
5,847✔
258
    }
2✔
259
}
2✔
260

261
TEST(EncryptedFile_GapsOfNeverWrittenPages)
262
{
2✔
263
    constexpr size_t page_count = 128;
2✔
264
    TEST_PATH(path);
2✔
265

266
    // Write to every other page. Note that on 16k systems this is actually
267
    // writing to 4 pages and then skipping 4 pages, which achieves the same
268
    // goal.
269
    {
2✔
270
        File f(path, File::mode_Write);
2✔
271
        f.set_encryption_key(test_util::crypt_key(true));
2✔
272
        f.resize(page_size() * page_count);
2✔
273
        for (size_t i = 0; i < page_count; i += 2) {
130✔
274
            File::Map<char> map(f, i * page_size(), File::access_ReadWrite, page_size());
128✔
275
            util::encryption_read_barrier(map, 0, page_size());
128✔
276
            std::fill(map.get_addr(), map.get_addr() + map.get_size(), 1);
128✔
277
            util::encryption_write_barrier(map, 0, page_size());
128✔
278
        }
128✔
279
    }
2✔
280

281
    // Trying to read via a single large read barrier should fail since it
282
    // includes never-written pages
283
    {
2✔
284
        File f(path, File::mode_Read);
2✔
285
        f.set_encryption_key(test_util::crypt_key(true));
2✔
286
        File::Map<char> map(f, 0, File::access_ReadOnly, page_count * page_size());
2✔
287
        CHECK_THROW(util::encryption_read_barrier(map, 0, map.get_size()), DecryptionFailed);
2✔
288
    }
2✔
289

290
    // A single large read mapping that only has barriers for the written pages
291
    // should work
292
    {
2✔
293
        File f(path, File::mode_Read);
2✔
294
        f.set_encryption_key(test_util::crypt_key(true));
2✔
295
        File::Map<char> map(f, 0, File::access_ReadOnly, page_count * page_size());
2✔
296
        for (size_t i = 0; i < page_count; i += 2) {
130✔
297
            util::encryption_read_barrier(map, i * page_size(), page_size());
128✔
298
            for (size_t j = 0; j < page_size(); ++j) {
1,310,848✔
299
                CHECK_EQUAL(int(map.get_addr()[i * page_size() + j]), 1);
1,310,720✔
300
            }
1,310,720✔
301
        }
128✔
302

303
        // And reading the unwritten pages should throw
304
        for (size_t i = 1; i < page_count; i += 2) {
130✔
305
            CHECK_THROW(util::encryption_read_barrier(map, 0, map.get_size()), DecryptionFailed);
128✔
306
        }
128✔
307
    }
2✔
308

309
    // Reading the whole thing via a write mapping should work, as those are
310
    // allowed to see uninitialized data
311
    {
2✔
312
        File f(path, File::mode_Update);
2✔
313
        f.set_encryption_key(test_util::crypt_key(true));
2✔
314
        File::Map<char> map(f, 0, File::access_ReadWrite, page_count * page_size());
2✔
315
        util::encryption_read_barrier(map, 0, map.get_size());
2✔
316

317
        for (size_t i = 0; i < page_count; ++i) {
258✔
318
            const int expected = (i + 1) % 2;
256✔
319
            for (size_t j = 0; j < page_size(); ++j) {
2,621,696✔
320
                CHECK_EQUAL(int(map.get_addr()[i * page_size() + j]), expected);
2,621,440✔
321
            }
2,621,440✔
322
        }
256✔
323
        util::encryption_write_barrier(map, 0, map.get_size());
2✔
324
    }
2✔
325
}
2✔
326

327
TEST(EncryptedFile_MultipleWriterMappings)
328
{
2✔
329
    const size_t count = 4096 * 64 * 2; // i.e. two metablocks of data
2✔
330
    const size_t increments = 100;
2✔
331
    TEST_PATH(path);
2✔
332

333
    {
2✔
334
        File w(path, File::mode_Write);
2✔
335
        w.set_encryption_key(test_util::crypt_key(true));
2✔
336
        w.resize(count);
2✔
337
        File::Map<char> map1(w, File::access_ReadWrite, count);
2✔
338
        File::Map<char> map2(w, File::access_ReadWrite, count);
2✔
339

340
        for (size_t i = 0; i < count; i += increments) {
10,488✔
341
            util::encryption_read_barrier(map1, i);
10,486✔
342
            map1.get_addr()[i] = 1;
10,486✔
343
            realm::util::encryption_write_barrier(map1, i);
10,486✔
344
        }
10,486✔
345

346
        // Since these are multiple mappings from one File, they should see
347
        // each other's writes without flushing in between
348
        for (size_t i = 0; i < count; i += increments) {
10,488✔
349
            util::encryption_read_barrier(map1, i, 1);
10,486✔
350
            ++map1.get_addr()[i];
10,486✔
351
            realm::util::encryption_write_barrier(map1, i);
10,486✔
352
            util::encryption_read_barrier(map2, i, 1);
10,486✔
353
            ++map2.get_addr()[i];
10,486✔
354
            realm::util::encryption_write_barrier(map2, i);
10,486✔
355
        }
10,486✔
356
    }
2✔
357

358
    File reader(path, File::mode_Read);
2✔
359
    reader.set_encryption_key(test_util::crypt_key(true));
2✔
360

361
    File::Map<char> read(reader, File::access_ReadOnly, count);
2✔
362
    util::encryption_read_barrier(read, 0, count);
2✔
363
    for (size_t i = 0; i < count; i += increments) {
10,488✔
364
        if (!CHECK_EQUAL(int(read.get_addr()[i]), 3))
10,486✔
NEW
365
            return;
×
366
    }
10,486✔
367
}
2✔
368

369
TEST(EncryptedFile_MultipleWriterFiles)
370
{
2✔
371
    const size_t count = 4096 * 64 * 2; // i.e. two metablocks of data
2✔
372
    const size_t increments = 100;
2✔
373
    TEST_PATH(path);
2✔
374

375
    {
2✔
376
        File w1(path, File::mode_Write);
2✔
377
        w1.set_encryption_key(test_util::crypt_key(true));
2✔
378
        w1.resize(count);
2✔
379
        File::Map<char> map1(w1, File::access_ReadWrite, count);
2✔
380

381
        File w2(path, File::mode_Update);
2✔
382
        w2.set_encryption_key(test_util::crypt_key(true));
2✔
383
        File::Map<char> map2(w2, File::access_ReadWrite, count);
2✔
384

385
        for (size_t i = 0; i < count; i += increments) {
10,488✔
386
            util::encryption_read_barrier(map1, i);
10,486✔
387
            map1.get_addr()[i] = 1;
10,486✔
388
            realm::util::encryption_write_barrier(map1, i);
10,486✔
389
        }
10,486✔
390
        map1.flush();
2✔
391

392
        for (size_t i = 0; i < count; i += increments) {
10,488✔
393
            util::encryption_read_barrier(map1, i, 1);
10,486✔
394
            ++map1.get_addr()[i];
10,486✔
395
            realm::util::encryption_write_barrier(map1, i);
10,486✔
396
            map1.flush();
10,486✔
397
            w2.get_encryption()->mark_data_as_possibly_stale();
10,486✔
398

399
            util::encryption_read_barrier(map2, i, 1);
10,486✔
400
            ++map2.get_addr()[i];
10,486✔
401
            realm::util::encryption_write_barrier(map2, i);
10,486✔
402
            map2.flush();
10,486✔
403
            w1.get_encryption()->mark_data_as_possibly_stale();
10,486✔
404
        }
10,486✔
405
    }
2✔
406

407
    File reader(path, File::mode_Read);
2✔
408
    reader.set_encryption_key(test_util::crypt_key(true));
2✔
409

410
    File::Map<char> read(reader, File::access_ReadOnly, count);
2✔
411
    util::encryption_read_barrier(read, 0, count);
2✔
412
    for (size_t i = 0; i < count; i += increments) {
10,488✔
413
        if (!CHECK_EQUAL(int(read.get_addr()[i]), 3))
10,486✔
NEW
414
            return;
×
415
    }
10,486✔
416
}
2✔
417

418
TEST(EncryptedFile_MultipleReaders)
419
{
2✔
420
    const size_t count = 4096 * 64 * 2; // i.e. two metablocks of data
2✔
421
    const size_t increments = 100;
2✔
422
    TEST_PATH(path);
2✔
423

424
    File w1(path, File::mode_Write);
2✔
425
    w1.set_encryption_key(test_util::crypt_key(true));
2✔
426
    w1.resize(count);
2✔
427
    File::Map<char> map1(w1, File::access_ReadWrite, count);
2✔
428
    File::Map<char> map2(w1, File::access_ReadOnly, count);
2✔
429

430
    File w2(path, File::mode_Read);
2✔
431
    w2.set_encryption_key(test_util::crypt_key(true));
2✔
432
    File::Map<char> map3(w2, File::access_ReadOnly, count);
2✔
433

434
    for (size_t i = 0; i < count; i += increments) {
10,488✔
435
        util::encryption_read_barrier(map1, i);
10,486✔
436
        map1.get_addr()[i] = 1;
10,486✔
437
        realm::util::encryption_write_barrier(map1, i);
10,486✔
438
    }
10,486✔
439
    map1.flush();
2✔
440

441
    // Bring both readers fully up to date
442
    util::encryption_read_barrier(map2, 0, count);
2✔
443
    util::encryption_read_barrier(map3, 0, count);
2✔
444

445
    for (size_t i = 0; i < count; i += increments) {
10,488✔
446
        util::encryption_read_barrier(map1, i, 1);
10,486✔
447
        ++map1.get_addr()[i];
10,486✔
448
        realm::util::encryption_write_barrier(map1, i);
10,486✔
449

450
        // map1 sees the new value because the write was performed via it
451
        // map2 was updated in the write barrier since it's the same File
452
        // map3 is viewing stale data but hasn't been told to refresh
453
        CHECK_EQUAL(map1.get_addr()[i], 2);
10,486✔
454
        CHECK_EQUAL(map2.get_addr()[i], 2);
10,486✔
455
        CHECK_EQUAL(map3.get_addr()[i], 1);
10,486✔
456

457
        // Read barrier is a no-op because of no call to mark_data_as_possibly_stale()
458
        util::encryption_read_barrier(map3, i, 1);
10,486✔
459
        CHECK_EQUAL(map3.get_addr()[i], 1);
10,486✔
460

461
        map1.flush(true);
10,486✔
462
        w2.get_encryption()->mark_data_as_possibly_stale();
10,486✔
463

464
        // Still see the old value since no read barrier
465
        CHECK_EQUAL(map3.get_addr()[i], 1);
10,486✔
466

467
        // Now finally brought up to date
468
        util::encryption_read_barrier(map3, i, 1);
10,486✔
469
        CHECK_EQUAL(map3.get_addr()[i], 2);
10,486✔
470
    }
10,486✔
471
}
2✔
472

473
TEST(EncryptedFile_IVsAreRereadOnlyWhenObserverIsPresent)
474
{
2✔
475
    TEST_PATH(path);
2✔
476
    const size_t page_size = 4096;
2✔
477
    const size_t size = page_size * 64;
2✔
478
    File w(path, File::mode_Write);
2✔
479
    w.set_encryption_key(test_util::crypt_key(true));
2✔
480
    w.resize(size);
2✔
481

482
    // Initialize all of the pages so iv1 is non-zero
483
    File::Map<char> map_w(w, File::access_ReadWrite, size);
2✔
484
    encryption_read_barrier(map_w, 0, size);
2✔
485
    encryption_write_barrier(map_w, 0, size);
2✔
486
    map_w.flush();
2✔
487

488
    File r(path, File::mode_Read);
2✔
489
    r.set_encryption_key(test_util::crypt_key(true));
2✔
490
    File::Map<char> map_r1(r, File::access_ReadOnly, size);
2✔
491
    File::Map<char> map_r2(r, File::access_ReadOnly, size);
2✔
492
    File::Map<char> map_r3(r, File::access_ReadOnly, size);
2✔
493

494
    struct : WriteObserver {
2✔
495
        bool no_concurrent_writer_seen() override
2✔
496
        {
2✔
497
            return true;
2✔
498
        }
2✔
499
    } r2_observer;
2✔
500
    map_r2.get_encrypted_mapping()->set_observer(&r2_observer);
2✔
501

502
    struct : WriteObserver {
2✔
503
        bool no_concurrent_writer_seen() override
2✔
504
        {
4✔
505
            return false;
4✔
506
        }
4✔
507
    } r3_observer;
2✔
508
    map_r3.get_encrypted_mapping()->set_observer(&r3_observer);
2✔
509

510
    // Reads the entire IV block and first page of data
511
    encryption_read_barrier(map_r1, 0, page_size);
2✔
512
    encryption_read_barrier(map_r2, 0, page_size);
2✔
513
    encryption_read_barrier(map_r3, 0, page_size);
2✔
514

515
    encryption_read_barrier(map_w, page_size, size - page_size);
2✔
516
    encryption_write_barrier(map_w, page_size, size - page_size);
2✔
517
    map_w.flush();
2✔
518

519
    // No observer, so it uses the cached IV/hmac
520
    CHECK_THROW(encryption_read_barrier(map_r1, page_size, 1), DecryptionFailed);
2✔
521
    // Observer says no concurrent writers, so it uses the cached IV/hmac
522
    CHECK_THROW(encryption_read_barrier(map_r2, page_size, 1), DecryptionFailed);
2✔
523
    // Observer says there are concurrent writers, so it rereads the IV after
524
    // decryption fails the first time
525
    encryption_read_barrier(map_r3, page_size, 1);
2✔
526
}
2✔
527

528
TEST(EncryptedFile_Truncation)
529
{
2✔
530
    TEST_PATH(path);
2✔
531
    const size_t page_size = 4096;
2✔
532
    const size_t size = page_size * 64;
2✔
533
    File w(path, File::mode_Write);
2✔
534
    w.set_encryption_key(test_util::crypt_key(true));
2✔
535
    w.resize(size);
2✔
536

537
    {
2✔
538
        // Initialize all of the pages so iv1 is non-zero
539
        File::Map<char> map(w, File::access_ReadWrite, size);
2✔
540
        encryption_read_barrier(map, 0, size);
2✔
541
        encryption_write_barrier(map, 0, size);
2✔
542
    }
2✔
543

544
    // Truncate and then re-expand the file
545
    w.resize(size / 2);
2✔
546
    w.resize(size);
2✔
547

548
    {
2✔
549
        File::Map<char> map(w, File::access_ReadOnly, size);
2✔
550
        // Trying to read the entire file fails because it's trying to read
551
        // uninitialized data
552
        CHECK_THROW(encryption_read_barrier(map, 0, size), DecryptionFailed);
2✔
553
        // Reading just the valid part works
554
        CHECK_NOTHROW(encryption_read_barrier(map, 0, size / 2));
2✔
555
    }
2✔
556

557
    {
2✔
558
        // Write mapping can read the entire file
559
        File::Map<char> map(w, File::access_ReadWrite, size);
2✔
560
        encryption_read_barrier(map, 0, size);
2✔
561
        encryption_write_barrier(map, 0, size);
2✔
562
    }
2✔
563
}
2✔
564

565
TEST(EncryptedFile_RacingReadAndWrite)
566
{
2✔
567
    TEST_PATH(path);
2✔
568
    static constexpr size_t page_size = 4096;
2✔
569
    static constexpr size_t page_count = 64;
2✔
570
    static constexpr size_t size = page_size * page_count;
2✔
571

572
    {
2✔
573
        // Initialize the file
574
        File w(path, File::mode_Write);
2✔
575
        w.set_encryption_key(test_util::crypt_key(true));
2✔
576
        w.resize(size);
2✔
577
        File::Map<char> map(w, File::access_ReadWrite, size);
2✔
578
        encryption_read_barrier(map, 0, size);
2✔
579
        encryption_write_barrier(map, 0, size);
2✔
580
        map.flush();
2✔
581
    }
2✔
582

583
    File w(path, File::mode_Update);
2✔
584
    // note: not setting encryption key
585
    // Flip some bits in the encrypted file to make it invalid
586
    for (File::SizeType pos = int(page_size); pos < w.get_size(); pos += page_size) {
132✔
587
        char c;
130✔
588
        w.read(pos, &c, 1);
130✔
589
        c = ~c;
130✔
590
        w.write(pos, &c, 1);
130✔
591
    }
130✔
592

593
    struct : WriteObserver {
2✔
594
        size_t page = 0;
2✔
595
        size_t count = 0;
2✔
596
        AESCryptor cryptor{test_util::crypt_key(true)};
2✔
597
        util::File* file;
2✔
598

599
        bool no_concurrent_writer_seen() override
2✔
600
        {
1,920✔
601
            // The first 15 read attempts we modify the page so that it
602
            // continues trying to reread past the normal limit of 5 attempts,
603
            // but we continue to leave the page in an invalid state
604
            if (++count < 15) {
1,920✔
605
                auto pos = (page + 1) * page_size + 1;
1,792✔
606
                char c;
1,792✔
607
                file->read(pos, &c, 1);
1,792✔
608
                ++c;
1,792✔
609
                file->write(pos, &c, 1);
1,792✔
610
                return false;
1,792✔
611
            }
1,792✔
612

613
            // Now we write valid encrypted data which will result in the
614
            // decryption succeeding
615
            count = 0;
128✔
616
            char buffer[page_size] = {0};
128✔
617
            cryptor.write(file->get_descriptor(), page * page_size, buffer);
128✔
618
            return false;
128✔
619
        }
1,920✔
620
    } observer;
2✔
621
    observer.file = &w;
2✔
622
    observer.cryptor.set_data_size(File::SizeType(size));
2✔
623

624
    File r(path, File::mode_Read);
2✔
625
    r.set_encryption_key(test_util::crypt_key(true));
2✔
626
    for (size_t i = 0; i < page_count; ++i) {
130✔
627
        observer.page = i;
128✔
628
        File::Map<char> map(r, i * page_size);
128✔
629
        map.get_encrypted_mapping()->set_observer(&observer);
128✔
630
        util::encryption_read_barrier(map, 0);
128✔
631
    }
128✔
632
}
2✔
633

634
#endif // REALM_ENABLE_ENCRYPTION
635
#endif // TEST_ENCRYPTED_FILE_MAPPING
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc