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

realm / realm-core / 2279

30 Apr 2024 03:09PM UTC coverage: 90.746% (+0.009%) from 90.737%
2279

push

Evergreen

web-flow
Build with -Werror on CI (#7646)

We used to have the Jenkins error parser that made the job fail if there were
any warnings, but now we have nothing and warnings can slip through. This is a
little worse as it makes the build fail immediately rather than letting it run
the tests, but it's better than nothing.

101976 of 180232 branches covered (56.58%)

3 of 5 new or added lines in 4 files covered. (60.0%)

54 existing lines in 10 files now uncovered.

212470 of 234138 relevant lines covered (90.75%)

6117931.14 hits per line

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

97.96
/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 uint8_t 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_file_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, sizeof(data));
2✔
80
    cryptor.read(file.get_descriptor(), 0, buffer, sizeof(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_file_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, sizeof(data));
2✔
95
    file.seek(0);
2✔
96
    ssize_t actual_read_1 = file.read(raw_buffer_1, sizeof(raw_buffer_1));
2✔
97
    CHECK_EQUAL(actual_read_1, sizeof(raw_buffer_1));
2✔
98

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

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

107
TEST(EncryptedFile_SeparateCryptors)
108
{
2✔
109
    TEST_PATH(path);
2✔
110

111
    const char data[4096] = "test data";
2✔
112
    char buffer[4096];
2✔
113

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

126
    CHECK(memcmp(buffer, data, strlen(data)) == 0);
2✔
127
}
2✔
128

129
TEST(EncryptedFile_InterruptedWrite)
130
{
2✔
131
    TEST_PATH(path);
2✔
132

133
    const char data[4096] = "test data";
2✔
134

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

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

148
    memcpy(buffer + 32, buffer, 32);
2✔
149
    buffer[5]++; // first byte of "hmac1" field in iv table
2✔
150
    file.seek(0);
2✔
151
    file.write(buffer, 64);
2✔
152

153
    {
2✔
154
        AESCryptor cryptor(test_key);
2✔
155
        cryptor.set_file_size(16);
2✔
156
        cryptor.read(file.get_descriptor(), 0, buffer, sizeof(buffer));
2✔
157
        CHECK(memcmp(buffer, data, strlen(data)) == 0);
2✔
158
    }
2✔
159
}
2✔
160

161
TEST(EncryptedFile_LargePages)
162
{
2✔
163
    TEST_PATH(path);
2✔
164

165
    char data[4096 * 4];
2✔
166
    for (size_t i = 0; i < sizeof(data); ++i)
32,770✔
167
        data[i] = static_cast<char>(i);
32,768✔
168

169
    AESCryptor cryptor(test_key);
2✔
170
    cryptor.set_file_size(sizeof(data));
2✔
171
    char buffer[sizeof(data)];
2✔
172

173
    File file(path, realm::util::File::mode_Write);
2✔
174
    cryptor.write(file.get_descriptor(), 0, data, sizeof(data));
2✔
175
    cryptor.read(file.get_descriptor(), 0, buffer, sizeof(buffer));
2✔
176
    CHECK(memcmp(buffer, data, sizeof(data)) == 0);
2✔
177
}
2✔
178

179
TEST(EncryptedFile_IVRefreshing)
180
{
2✔
181
    using IVPageStates = realm::util::FlatMap<size_t, IVRefreshState>;
2✔
182
    constexpr size_t block_size = 4096;
2✔
183
    constexpr size_t blocks_per_metadata_block = 64;
2✔
184
    const size_t pages_per_metadata_block = block_size * blocks_per_metadata_block / page_size();
2✔
185

186
    auto verify_page_states = [&](const IVPageStates& states, off_t data_pos,
2✔
187
                                  std::vector<size_t> expected_pages_refreshed) {
30✔
188
        size_t start_page_ndx = ((data_pos / block_size) / blocks_per_metadata_block) * blocks_per_metadata_block *
30✔
189
                                block_size / page_size();
30✔
190
        size_t end_page_ndx = (((data_pos / block_size) + blocks_per_metadata_block) / blocks_per_metadata_block) *
30✔
191
                              blocks_per_metadata_block * block_size / page_size();
30✔
192

193
        CHECK_EQUAL(states.size(), end_page_ndx - start_page_ndx);
30✔
194
        for (size_t ndx = start_page_ndx; ndx < end_page_ndx; ++ndx) {
1,230✔
195
            CHECK_EQUAL(states.count(ndx), 1);
1,200✔
196
            bool expected_refresh = std::find(expected_pages_refreshed.begin(), expected_pages_refreshed.end(),
1,200✔
197
                                              ndx) != expected_pages_refreshed.end();
1,200✔
198
            CHECK(states.at(ndx) == (expected_refresh ? IVRefreshState::RequiresRefresh : IVRefreshState::UpToDate));
1,200✔
199
        }
1,200✔
200
    };
30✔
201

202
    TEST_PATH(path);
2✔
203
    // enough data to span two metadata blocks
204
    constexpr size_t data_size = block_size * blocks_per_metadata_block * 2;
2✔
205
    const size_t num_pages = data_size / page_size();
2✔
206
    char data[block_size];
2✔
207
    for (size_t i = 0; i < sizeof(data); ++i)
8,194✔
208
        data[i] = static_cast<char>(i);
8,192✔
209

210
    AESCryptor cryptor(test_key);
2✔
211
    cryptor.set_file_size(off_t(data_size));
2✔
212
    File file(path, realm::util::File::mode_Write);
2✔
213
    const FileDesc fd = file.get_descriptor();
2✔
214

215
    auto make_external_write_at_pos = [&](off_t data_pos) -> size_t {
16✔
216
        const size_t begin_write_block = data_pos / block_size * block_size;
16✔
217
        const size_t ndx_in_block = data_pos % block_size;
16✔
218
        AESCryptor cryptor2(test_key);
16✔
219
        cryptor2.set_file_size(off_t(data_size));
16✔
220
        cryptor2.read(fd, off_t(begin_write_block), data, block_size);
16✔
221
        ++data[ndx_in_block];
16✔
222
        cryptor2.write(fd, off_t(begin_write_block), data, block_size);
16✔
223
        return data_pos / page_size();
16✔
224
    };
16✔
225

226
    for (size_t i = 0; i < data_size; i += block_size) {
258✔
227
        cryptor.write(fd, off_t(i), data, block_size);
256✔
228
    }
256✔
229

230
    IVPageStates states = cryptor.refresh_ivs(fd, 0, 0, num_pages);
2✔
231
    std::vector<size_t> pages_needing_refresh = {};
2✔
232
    for (size_t i = 0; i < pages_per_metadata_block; ++i) {
82✔
233
        pages_needing_refresh.push_back(i);
80✔
234
    }
80✔
235
    // initial call requires refreshing all pages in range
236
    verify_page_states(states, 0, pages_needing_refresh);
2✔
237
    states = cryptor.refresh_ivs(fd, 0, 0, num_pages);
2✔
238
    // subsequent call does not require refreshing anything
239
    verify_page_states(states, 0, {});
2✔
240

241
    pages_needing_refresh = {};
2✔
242
    for (size_t i = 0; i < pages_per_metadata_block; ++i) {
82✔
243
        pages_needing_refresh.push_back(i + pages_per_metadata_block);
80✔
244
    }
80✔
245
    off_t read_data_pos = off_t(pages_per_metadata_block * page_size());
2✔
246
    states = cryptor.refresh_ivs(fd, read_data_pos, pages_per_metadata_block, num_pages);
2✔
247
    verify_page_states(states, read_data_pos, pages_needing_refresh);
2✔
248
    states = cryptor.refresh_ivs(fd, read_data_pos, pages_per_metadata_block, num_pages);
2✔
249
    verify_page_states(states, read_data_pos, {});
2✔
250

251
    read_data_pos = off_t(data_size / 2);
2✔
252
    size_t read_page_ndx = read_data_pos / page_size();
2✔
253
    states = cryptor.refresh_ivs(fd, read_data_pos, read_page_ndx, num_pages);
2✔
254
    verify_page_states(states, read_data_pos, {});
2✔
255

256
    read_data_pos = off_t(data_size - 1);
2✔
257
    read_page_ndx = read_data_pos / page_size();
2✔
258
    states = cryptor.refresh_ivs(fd, read_data_pos, read_page_ndx, num_pages);
2✔
259
    verify_page_states(states, read_data_pos, {});
2✔
260

261
    // write at pos 0, read half way through the first page
262
    make_external_write_at_pos(0);
2✔
263
    read_data_pos = off_t(page_size() / 2);
2✔
264
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
265
    verify_page_states(states, read_data_pos, {0});
2✔
266

267
    // write at end of first page, read half way through first page
268
    make_external_write_at_pos(off_t(page_size() - 1));
2✔
269
    read_data_pos = off_t(page_size() / 2);
2✔
270
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
271
    verify_page_states(states, read_data_pos, {0});
2✔
272

273
    // write at beginning of second page, read in first page
274
    make_external_write_at_pos(off_t(page_size()));
2✔
275
    read_data_pos = off_t(page_size() / 2);
2✔
276
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
277
    verify_page_states(states, read_data_pos, {1});
2✔
278

279
    // write at last page of first metadata block, read in first page
280
    size_t page_needing_refresh = make_external_write_at_pos(blocks_per_metadata_block * block_size - 1);
2✔
281
    read_data_pos = off_t(page_size() / 2);
2✔
282
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
283
    verify_page_states(states, read_data_pos, {page_needing_refresh});
2✔
284

285
    // test truncation of end_page: write to first page, and last page of first metadata block, read in first page,
286
    // but set the end page index lower than the last write
287
    make_external_write_at_pos(0);
2✔
288
    page_needing_refresh = make_external_write_at_pos(blocks_per_metadata_block * block_size - 1);
2✔
289
    REALM_ASSERT(page_needing_refresh >= 1); // this test assumes page_size is < 64 * block_size
2✔
290
    read_data_pos = off_t(0);
2✔
291
    constexpr size_t end_page_index = 1;
2✔
292
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, end_page_index);
2✔
293
    CHECK_EQUAL(states.size(), 1);
2✔
294
    CHECK_EQUAL(states.count(size_t(0)), 1);
2✔
295
    CHECK(states[0] == IVRefreshState::RequiresRefresh);
2✔
296
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
297
    verify_page_states(states, 0, {page_needing_refresh});
2✔
298

299
    // write to a block indexed to the second metadata block
300
    page_needing_refresh = make_external_write_at_pos(blocks_per_metadata_block * block_size);
2✔
301
    // a read anywhere in the first metadata block domain does not require refresh
302
    read_data_pos = off_t(page_size() / 2);
2✔
303
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
304
    verify_page_states(states, read_data_pos, {});
2✔
305
    // but a read in a page controlled by the second metadata block does require a refresh
306
    read_data_pos = off_t(blocks_per_metadata_block * block_size);
2✔
307
    states = cryptor.refresh_ivs(fd, read_data_pos, page_needing_refresh, num_pages);
2✔
308
    verify_page_states(states, read_data_pos, {page_needing_refresh});
2✔
309

310
    // write to the last byte of data
311
    page_needing_refresh = make_external_write_at_pos(data_size - 1);
2✔
312
    // a read anywhere in the first metadata block domain does not require refresh
313
    read_data_pos = 0;
2✔
314
    states = cryptor.refresh_ivs(fd, read_data_pos, 0, num_pages);
2✔
315
    verify_page_states(states, read_data_pos, {});
2✔
316
    // but a read in a page controlled by the second metadata block does require a refresh
317
    read_data_pos = off_t(data_size - 1);
2✔
318
    states = cryptor.refresh_ivs(fd, read_data_pos, page_needing_refresh, num_pages);
2✔
319
    verify_page_states(states, read_data_pos, {page_needing_refresh});
2✔
320
}
2✔
321

322
static void check_attach_and_read(const char* key, const std::string& path, size_t num_entries)
323
{
606✔
324
    try {
606✔
325
        auto hist = make_in_realm_history();
606✔
326
        DBOptions options(key);
606✔
327
        auto sg = DB::create(*hist, path, options);
606✔
328
        auto rt = sg->start_read();
606✔
329
        auto foo = rt->get_table("foo");
606✔
330
        auto pk_col = foo->get_primary_key_column();
606✔
331
        REALM_ASSERT_3(foo->size(), ==, num_entries);
606✔
332
        REALM_ASSERT_3(foo->where().equal(pk_col, util::format("name %1", num_entries - 1).c_str()).count(), ==, 1);
606✔
333
    }
606✔
334
    catch (const std::exception& e) {
606✔
NEW
335
        auto fs = File::get_size_static(path);
×
336
        util::format(std::cout, "Error for num_entries %1 with page_size of %2 on file of size %3\n%4", num_entries,
×
337
                     page_size(), fs, e.what());
×
NEW
338
        throw;
×
339
    }
×
340
}
606✔
341

342
// This test changes the global page_size() and should not run with other tests.
343
// It checks that an encrypted Realm is portable between systems with a different page size
344
NONCONCURRENT_TEST(EncryptedFile_Portablility)
345
{
2✔
346
    const char* key = test_util::crypt_key(true);
2✔
347
    // The idea here is to incrementally increase the allocations in the Realm
348
    // such that the top ref written eventually crosses over the block_size and
349
    // page_size() thresholds. This has caught faulty top_ref + size calculations.
350
    std::vector<size_t> test_sizes;
2✔
351
#if TEST_DURATION == 0
2✔
352
    test_sizes.resize(100);
2✔
353
    std::iota(test_sizes.begin(), test_sizes.end(), 500);
2✔
354
    // The allocations are not controlled, but at the time of writing this test
355
    // 539 objects produced a file of size 16384 while 540 objects produced a file of size 20480
356
    // so at least one threshold is crossed here, though this may change if the allocator changes
357
    // or if compression is implemented
358
#else
359
    test_sizes.resize(5000);
360
    std::iota(test_sizes.begin(), test_sizes.end(), 500);
361
#endif
362

363
    test_sizes.push_back(1); // check the lower limit
2✔
364
    for (auto num_entries : test_sizes) {
202✔
365
        TEST_PATH(path);
202✔
366
        {
202✔
367
            // create the Realm with the smallest supported page_size() of 4096
368
            OnlyForTestingPageSizeChange change_page_size(4096);
202✔
369
            Group g;
202✔
370
            TableRef foo = g.add_table_with_primary_key("foo", type_String, "name", false);
202✔
371
            for (size_t i = 0; i < num_entries; ++i) {
110,104✔
372
                foo->create_object_with_primary_key(util::format("name %1", i));
109,902✔
373
            }
109,902✔
374
            g.write(path, key);
202✔
375
            // size_t fs = File::get_size_static(path);
376
            // util::format(std::cout, "write of %1 objects produced a file of size %2\n", num_entries, fs);
377
        }
202✔
378
        {
202✔
379
            OnlyForTestingPageSizeChange change_page_size(8192);
202✔
380
            check_attach_and_read(key, path, num_entries);
202✔
381
        }
202✔
382
        {
202✔
383
            OnlyForTestingPageSizeChange change_page_size(16384);
202✔
384
            check_attach_and_read(key, path, num_entries);
202✔
385
        }
202✔
386

387
        // check with the native page_size (which is probably redundant with one of the above)
388
        // and check that a write works correctly
389
        auto history = make_in_realm_history();
202✔
390
        DBOptions options(key);
202✔
391
        DBRef db = DB::create(*history, path, options);
202✔
392
        auto wt = db->start_write();
202✔
393
        TableRef bar = wt->get_or_add_table_with_primary_key("bar", type_String, "pk");
202✔
394
        bar->create_object_with_primary_key("test");
202✔
395
        wt->commit();
202✔
396
        check_attach_and_read(key, path, num_entries);
202✔
397
    }
202✔
398
}
2✔
399

400
#endif // REALM_ENABLE_ENCRYPTION
401
#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