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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

90.24
/src/realm/util/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 <realm/util/encrypted_file_mapping.hpp>
20

21
#include <realm/util/backtrace.hpp>
22
#include <realm/util/file_mapper.hpp>
23

24
#include <sstream>
25

26
#if REALM_ENABLE_ENCRYPTION
27
#include <realm/util/aes_cryptor.hpp>
28
#include <realm/util/errno.hpp>
29
#include <realm/util/sha_crypto.hpp>
30
#include <realm/util/terminate.hpp>
31
#include <realm/utilities.hpp>
32

33
#include <algorithm>
34
#include <array>
35
#include <chrono>
36
#include <cstdlib>
37
#include <cstring>
38
#include <iostream>
39
#include <string_view>
40
#include <thread>
41

42
#ifdef REALM_DEBUG
43
#include <cstdio>
44
#endif
45

46
#if defined(_WIN32)
47
#include <Windows.h>
48
#include <bcrypt.h>
49
#pragma comment(lib, "bcrypt.lib")
50
#else
51
#include <sys/mman.h>
52
#include <unistd.h>
53
#endif
54

55
namespace realm::util {
56
// When Realm's file encryption was originally designed, we had the constraint
57
// that all encryption and decryption had to happen in aligned system page size
58
// sized blocks due to the use of signal handlers to lazily decrypt data and
59
// track where writes occurrs. This is no longer the case, but may still help
60
// explain why the file layout looks the way it does.
61
//
62
// Encryption is performed on 4096 byte data pages. Each group of 64 data pages
63
// is arranged into a "block", which has a 4096 byte header containing the IVs
64
// and HMACs for the following pages. Each page has *two* IVs and HMACs stored.
65
// iv2/hmac2 contain the values which were last used to successfully decrypt
66
// the page, while iv1/hmac1 is the values which were used to last encrypt the
67
// page.
68
//
69
// Writing new encrypted data has the following steps:
70
//
71
// 1. Copy iv1/hmac1 to iv2/hmac2 in the IVTable
72
// 2. Increment iv1
73
// 3. Encrypt the page in memory
74
// 4. Compute the hmac for the new encrypted data.
75
// 5. If the hmac matches the previous hmac, goto 2 (this will not ever actually happen)
76
// 6. Write the new IVTable for the page.
77
// 7. fsync() (or F_BARRIERFSYNC on Apple)
78
// 8. Write the new encrypted data
79
//
80
// If we are interrupted before #6, no i/o has happened and the data on disk is
81
// fine. If we are interrupted between #6 and #8, then when we next try to read
82
// the page the hmac check using hmac1 will fail, but the check using hmac2
83
// will succeed and we will be able to read the old data. We then copy
84
// iv2/hmac2 back to the active fields and continue as normal.
85
//
86
// This scheme breaks if we have a partial write of the 4k page. This is
87
// impossible with SSDs, which can only write in their atomic block size, and
88
// it would be extremely unusual for that to be smaller than 4k. It may be a
89
// problem when running on HDDs, though.
90
//
91
// Reading from an encrypted file is done by creating a mapping and then
92
// calling `read_barrier(addr, size)` to mark the section of the mapping which
93
// needs to be populated. This decrypts each of the pages which cover that
94
// range and places the plaintext into memory. If any of the pages were already
95
// decrypted, this is a no-op that skips reading anything and just assumes that
96
// the data was up-to-date.
97
//
98
// Writing is done with `read_barrier(addr, size, true)` before performing any
99
// writes to mark the range as writeable, and then `write_barrier(addr, size)`
100
// to mark bytes which were actually written to. `write_barrier()` eagerly
101
// copies all of the written bytes to any other active mappings on the same
102
// file which have those pages decrypted in memory. This is spooky
103
// threading-wise, and is only made safe by Realm's MVCC semantics - if we're
104
// writing to a section of the file we know that no one can be legally reading
105
// those exact bytes, and we must be writing to different bytes in the same
106
// page. This copying makes it so that we never have to recheck the disk; once
107
// we have read and decrypted a page for a mapping, that page is forevermore
108
// valid and up-to-date.
109
//
110
// All dirty data is kept buffered in memory until `flush()` is called.
111
//
112
// In multi-process scenarios (or just multiple File instances for a single
113
// file in a single process, which doesn't happen when using the public API
114
// normally), eagerly keeping decrypted pages up to date is impossible, and we
115
// sometimes need to recheck the disk. Here we once again take advantage of
116
// Realm being MVCC with discrete points where we may need to see newer
117
// versions of the data on disk. When the reader view is updated, if there have
118
// been any external writes to the file SlabAlloc calls
119
// `mark_pages_for_iv_check()`, which puts all up-to-date pages into a
120
// potentially-stale state. The next time each page is accessed, we reread the
121
// IVTable for that page. If it's the same as the IVTable for the plaintext we
122
// have in memory then the page is marked as being up-to-date, and if it's
123
// different we reread the page.
124
//
125
// Another source of complexity in multiprocess scenarios is that while we
126
// assume that the actual i/o is atomic in 4k chunks, writing to the in-memory
127
// buffers is distinctly not atomic. One process reading from a memory mapping
128
// while another process is writing to that position in the file can see
129
// incomplete writes. Rather than doing page-level locking, we assume that this
130
// will be very rare and perform optimistic unlocked reads. If decryption fails
131
// and we are in a potentially-multiprocess scenario we retry the read several
132
// times before reporting an error.
133

134
struct IVTable {
135
    uint32_t iv1 = 0;
136
    std::array<uint8_t, 28> hmac1 = {};
137
    uint32_t iv2 = 0;
138
    std::array<uint8_t, 28> hmac2 = {};
139
    bool operator==(const IVTable& other) const
140
    {
72,261✔
141
        return iv1 == other.iv1 && iv2 == other.iv2 && hmac1 == other.hmac1 && hmac2 == other.hmac2;
72,261✔
142
    }
72,261✔
143
    bool operator!=(const IVTable& other) const
144
    {
72,261✔
145
        return !(*this == other);
72,261✔
146
    }
72,261✔
147
};
148
// We read this via memcpy and need it to be packed
149
static_assert(sizeof(IVTable) == 64);
150

151
namespace {
152
constexpr uint8_t aes_block_size = 16;
153
constexpr uint16_t encryption_page_size = 4096;
154
constexpr uint8_t metadata_size = sizeof(IVTable);
155
constexpr uint8_t pages_per_block = encryption_page_size / metadata_size;
156
static_assert(metadata_size == 64,
157
              "changing the size of the metadata breaks compatibility with existing Realm files");
158

159
using SizeType = File::SizeType;
160

161
template <typename To, typename From>
162
To checked_cast(From from)
163
{
164
    To to;
165
    if (REALM_UNLIKELY(int_cast_with_overflow_detect(from, to))) {
166
        throw MaximumFileSizeExceeded(util::format("File size %1 is larger than can be represented", from));
167
    }
168
    return to;
169
}
170

171
// Overflows when converting from file positions (always 64-bits) to size_t
172
// (sometimes 32-bits) should all be caught by set_file_size()
173
template <typename To, typename From>
174
constexpr To assert_cast(From from)
175
{
2,230,575✔
176
    REALM_ASSERT_DEBUG(!int_cast_has_overflow<To>(from));
2,230,575✔
177
    return static_cast<To>(from);
2,230,575✔
178
}
2,230,575✔
179

180
// Index of page which contains `data_pos`
181
constexpr size_t page_index(SizeType data_pos) noexcept
182
{
2,219,196✔
183
    SizeType index = data_pos / encryption_page_size;
2,219,196✔
184
    return assert_cast<size_t>(index);
2,219,196✔
185
}
2,219,196✔
186

187
// Number of pages required to store `size` bytes
188
constexpr size_t page_count(SizeType size) noexcept
189
{
11,379✔
190
    return assert_cast<size_t>((size + encryption_page_size - 1) / encryption_page_size);
11,379✔
191
}
11,379✔
192

193
// Index of the metadata block which contains `data_pos`
194
constexpr size_t block_index(SizeType data_pos) noexcept
195
{
1,394,682✔
196
    return page_index(data_pos) / pages_per_block;
1,394,682✔
197
}
1,394,682✔
198

199
// Number of metadata blocks required to store `size` bytes
200
constexpr size_t block_count(SizeType data_size) noexcept
201
{
11,190✔
202
    return (page_count(data_size) + pages_per_block - 1) / pages_per_block;
11,190✔
203
}
11,190✔
204

205
// map an offset in the data to the actual location in the file
206
SizeType data_pos_to_file_pos(SizeType data_pos)
207
{
484,011✔
208
    REALM_ASSERT(data_pos >= 0);
484,011✔
209
    return data_pos + (block_index(data_pos) + 1) * encryption_page_size;
484,011✔
210
}
484,011✔
211

212
// map a location in the file to the offset in the data
213
SizeType file_pos_to_data_pos(SizeType file_pos)
214
{
2,379✔
215
    REALM_ASSERT(file_pos >= 0);
2,379✔
216
    const size_t metadata_page_count = (page_index(file_pos) + pages_per_block) / (pages_per_block + 1);
2,379✔
217
    return file_pos - metadata_page_count * encryption_page_size;
2,379✔
218
}
2,379✔
219

220
// get the location of the IVTable for the given data (not file) position
221
SizeType iv_table_pos(SizeType data_pos)
222
{
138,282✔
223
    REALM_ASSERT(data_pos >= 0);
138,282✔
224
    const size_t index = page_index(data_pos);
138,282✔
225
    const size_t metadata_block = block_index(data_pos);
138,282✔
226
    const size_t metadata_index = index & (pages_per_block - 1);
138,282✔
227
    return metadata_block * (pages_per_block + 1) * encryption_page_size + metadata_index * metadata_size;
138,282✔
228
}
138,282✔
229

230
// get the file location of the IVTable block for the given data (not file) position
231
SizeType iv_table_block_pos(SizeType data_pos)
232
{
77,241✔
233
    REALM_ASSERT(data_pos >= 0);
77,241✔
234
    return block_index(data_pos) * (pages_per_block + 1) * encryption_page_size;
77,241✔
235
}
77,241✔
236

237
constexpr size_t iv_table_size(SizeType data_pos)
238
{
11,190✔
239
    return block_count(data_pos) * pages_per_block;
11,190✔
240
}
11,190✔
241

242
// not actually checked any more
243
size_t check_read(FileDesc fd, SizeType pos, void* dst)
244
{
422,559✔
245
    return File::read_static(fd, pos, static_cast<char*>(dst), encryption_page_size);
422,559✔
246
}
422,559✔
247

248
// first block is iv data, second page is data
249
static_assert(c_min_encrypted_file_size == 2 * encryption_page_size,
250
              "chaging the block size breaks encrypted file portability");
251

252
template <class T, size_t N, std::size_t... I>
253
constexpr std::array<T, N> to_array_impl(const T* ptr, std::index_sequence<I...>)
254
{
348✔
255
    return {{ptr[I]...}};
348✔
256
}
348✔
257
template <class T, size_t N>
258
constexpr auto to_array(const T* ptr)
259
{
348✔
260
    return to_array_impl<T, N>(ptr, std::make_index_sequence<N>{});
348✔
261
}
348✔
262

263
void memcpy_if_changed(void* dst, const void* src, size_t n)
264
{
418,959✔
265
#if REALM_SANITIZE_THREAD
266
    // Because our copying is page-level granularity, we have some benign races
267
    // where the byte ranges in each page that weren't modified get overwritten
268
    // with the same values as they already had. TSan correctly reports this as
269
    // a data race, so when using TSan do (much slower) byte-level checking for
270
    // modifications and only write the ones which changed. Unlike suppressing
271
    // the warning entirely, this will still produce tsan errors if we actually
272
    // change any bytes that another thread is reading.
273
    auto dst_2 = static_cast<char*>(dst);
274
    auto src_2 = static_cast<const char*>(src);
275
    for (size_t i = 0; i < n; ++i) {
276
        if (dst_2[i] != src_2[i])
277
            dst_2[i] = src_2[i];
278
    }
279
#else
280
    memcpy(dst, src, n);
418,959✔
281
#endif
418,959✔
282
}
418,959✔
283

284
} // anonymous namespace
285

286
AESCryptor::AESCryptor(const char* key)
287
    : m_key(to_array<uint8_t, 64>(reinterpret_cast<const uint8_t*>(key)))
348✔
288
    , m_rw_buffer(new char[encryption_page_size])
348✔
289
    , m_dst_buffer(new char[encryption_page_size])
348✔
290
{
348✔
291
#if REALM_PLATFORM_APPLE
292
    // A random iv is passed to CCCryptorReset. This iv is *not used* by Realm; we set it manually prior to
293
    // each call to BCryptEncrypt() and BCryptDecrypt(). We pass this random iv as an attempt to
294
    // suppress a false encryption security warning from the IBM Bluemix Security Analyzer (PR[#2911])
295
    unsigned char u_iv[kCCKeySizeAES256];
296
    arc4random_buf(u_iv, kCCKeySizeAES256);
297
    void* iv = u_iv;
298
    CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_encr);
299
    CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_decr);
300
#elif defined(_WIN32)
301
    BCRYPT_ALG_HANDLE hAesAlg = NULL;
302
    int ret;
303
    ret = BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
304
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptOpenAlgorithmProvider()", ret);
305

306
    ret = BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC,
307
                            sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
308
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptSetProperty()", ret);
309

310
    ret = BCryptGenerateSymmetricKey(hAesAlg, &m_aes_key_handle, nullptr, 0, (PBYTE)key, 32, 0);
311
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptGenerateSymmetricKey()", ret);
312
#else
313
    m_ctx = EVP_CIPHER_CTX_new();
348✔
314
    if (!m_ctx)
348✔
315
        handle_error();
×
316
#endif
348✔
317
}
348✔
318

319
AESCryptor::~AESCryptor() noexcept
320
{
348✔
321
#if REALM_PLATFORM_APPLE
322
    CCCryptorRelease(m_encr);
323
    CCCryptorRelease(m_decr);
324
#elif defined(_WIN32)
325
#else
326
    EVP_CIPHER_CTX_cleanup(m_ctx);
348✔
327
    EVP_CIPHER_CTX_free(m_ctx);
348✔
328
#endif
348✔
329
}
348✔
330

331
void AESCryptor::handle_error()
332
{
×
333
    throw std::runtime_error("Error occurred in encryption layer");
×
334
}
×
335

336
void AESCryptor::set_data_size(SizeType new_data_size)
337
{
11,190✔
338
    REALM_ASSERT(new_data_size >= 0);
11,190✔
339
    m_iv_buffer.reserve(iv_table_size(new_data_size));
11,190✔
340
    m_iv_buffer_cache.reserve(m_iv_buffer.capacity());
11,190✔
341
    m_iv_blocks_read.resize(m_iv_buffer.capacity() / 64);
11,190✔
342
}
11,190✔
343

344
IVTable& AESCryptor::get_iv_table(FileDesc fd, SizeType data_pos, IVLookupMode mode) noexcept
345
{
545,580✔
346
    size_t idx = page_index(data_pos);
545,580✔
347
    REALM_ASSERT(idx < m_iv_buffer.capacity()); // required space should have been preallocated
545,580✔
348
    if (mode != IVLookupMode::UseCache || idx >= m_iv_buffer.size() || !m_iv_blocks_read[block_index(data_pos)]) {
545,580✔
349
        read_iv_block(fd, data_pos);
8,571✔
350
    }
8,571✔
351
    m_iv_buffer_cache[idx] = m_iv_buffer[idx];
545,580✔
352
    return m_iv_buffer[idx];
545,580✔
353
}
545,580✔
354

355
// We always read an entire block of IVTables at a time rather than just the
356
// one we need as it's likely to take about the same amount of time up front
357
// and greatly reduce the total number of read calls we have to make
358
void AESCryptor::read_iv_block(FileDesc fd, SizeType data_pos)
359
{
77,241✔
360
    size_t idx = block_index(data_pos) * pages_per_block;
77,241✔
361
    if (idx + pages_per_block > m_iv_buffer.size()) {
77,241✔
362
        m_iv_buffer.resize(idx + pages_per_block);
2,169✔
363
        m_iv_buffer_cache.resize(m_iv_buffer.size());
2,169✔
364
    }
2,169✔
365
    SizeType iv_pos = iv_table_block_pos(data_pos);
77,241✔
366
    check_read(fd, iv_pos, &m_iv_buffer[idx]);
77,241✔
367
    m_iv_blocks_read[block_index(data_pos)] = true;
77,241✔
368
}
77,241✔
369

370
void AESCryptor::calculate_hmac(Hmac& hmac) const
371
{
345,315✔
372
    hmac_sha224(Span(reinterpret_cast<const uint8_t*>(m_rw_buffer.get()), encryption_page_size), hmac,
345,315✔
373
                Span(m_key).sub_span<32>());
345,315✔
374
}
345,315✔
375

376
bool AESCryptor::constant_time_equals(const Hmac& a, const Hmac& b) const
377
{
345,405✔
378
    // Constant-time memcmp to avoid timing attacks
379
    uint8_t result = 0;
345,405✔
380
    for (size_t i = 0; i < a.size(); ++i)
10,016,568✔
381
        result |= a[i] ^ b[i];
9,671,163✔
382
    return result == 0;
345,405✔
383
}
345,405✔
384

385
bool AESCryptor::refresh_iv(FileDesc fd, size_t page_ndx)
386
{
72,261✔
387
    REALM_ASSERT(page_ndx < m_iv_buffer.capacity());
72,261✔
388
    if (page_ndx >= m_iv_buffer.size() || !m_iv_blocks_read[page_ndx / pages_per_block]) {
72,261✔
389
        read_iv_block(fd, SizeType(page_ndx) * encryption_page_size);
68,670✔
390
    }
68,670✔
391

392
    if (m_iv_buffer[page_ndx] != m_iv_buffer_cache[page_ndx]) {
72,261✔
393
        m_iv_buffer_cache[page_ndx] = m_iv_buffer[page_ndx];
66,567✔
394
        return true;
66,567✔
395
    }
66,567✔
396
    return false;
5,694✔
397
}
72,261✔
398

399
void AESCryptor::invalidate_ivs() noexcept
400
{
72,405✔
401
    m_iv_blocks_read.assign(m_iv_blocks_read.size(), false);
72,405✔
402
}
72,405✔
403

404
AESCryptor::ReadResult AESCryptor::read(FileDesc fd, SizeType pos, char* dst, WriteObserver* observer)
405
{
404,553✔
406
    uint32_t iv = 0;
404,553✔
407
    Hmac hmac;
404,553✔
408
    // We're in a single-process scenario (or other processes are only reading),
409
    // so we can trust our in-memory caches and never need to retry
410
    if (!observer || observer->no_concurrent_writer_seen()) {
404,553✔
411
        return attempt_read(fd, pos, dst, IVLookupMode::UseCache, iv, hmac);
403,959✔
412
    }
403,959✔
413

414
    // There's another process which might be trying to write to the file while
415
    // we're reading from it, which means that we might see invalid data due to
416
    // data races. When this happens we need to retry the read, and only throw
417
    // an error if the data either hasn't changed after the timeout has expired
418
    // or if we're in a reader starvation scenario where the writer is producing
419
    // new data faster than we can consume it.
420
    size_t retry_count = 0;
594✔
421
    std::pair<uint32_t, Hmac> last_iv_and_data_hash;
594✔
422
    auto retry_start_time = std::chrono::steady_clock::now();
594✔
423
    size_t num_identical_reads = 1;
594✔
424
    ReadResult result = ReadResult::Success;
594✔
425
    while (retry_count <= 5 || (retry_count - num_identical_reads > 1 && retry_count < 20)) {
3,339✔
426
        result =
3,339✔
427
            attempt_read(fd, pos, dst, retry_count == 0 ? IVLookupMode::UseCache : IVLookupMode::Refetch, iv, hmac);
3,339✔
428
        switch (result) {
3,339✔
429
            case ReadResult::Success:
576✔
430
            case ReadResult::Eof:
576✔
431
            case ReadResult::Uninitialized:
576✔
432
                // Consistent and valid states that may or may not actually have data
433
                return result;
576✔
434
            case ReadResult::InterruptedFirstWrite:
2,691✔
435
            case ReadResult::StaleHmac:
2,691✔
436
            case ReadResult::Failed:
2,763✔
437
                // Inconsistent states which may change if we retry
438
                break;
2,763✔
439
        }
3,339✔
440

441
        // Check if we've timed out, but always retry at least once in case
442
        // we got suspended while another process was writing or something
443
        constexpr auto max_retry_period = std::chrono::seconds(5);
2,763✔
444
        auto elapsed = std::chrono::steady_clock::now() - retry_start_time;
2,763✔
445
        if (retry_count > 0 && elapsed > max_retry_period) {
2,763✔
446
            auto str = util::format("unable to decrypt after %1 seconds (retry_count=%2)",
×
447
                                    std::chrono::duration_cast<std::chrono::seconds>(elapsed).count(), retry_count);
×
448
            // std::cerr << std::endl << "*Timeout: " << str << std::endl;
449
            throw DecryptionFailed(str);
×
450
        }
×
451

452
        // don't wait on the first retry as we want to optimize the case where the first read
453
        // from the iv table cache didn't validate and we are fetching the iv block from disk for the first time
454
        std::pair cur_iv_and_data_hash(iv, hmac);
2,763✔
455
        if (retry_count != 0) {
2,763✔
456
            if (last_iv_and_data_hash == cur_iv_and_data_hash) {
2,550✔
457
                ++num_identical_reads;
54✔
458
            }
54✔
459
            // don't retry right away if there are potentially other external writers
460
            std::this_thread::yield();
2,550✔
461
        }
2,550✔
462
        last_iv_and_data_hash = cur_iv_and_data_hash;
2,763✔
463
        ++retry_count;
2,763✔
464

465
        if (observer->no_concurrent_writer_seen())
2,763✔
466
            break;
18✔
467
    }
2,763✔
468

469
    return result;
18✔
470
}
594✔
471

472
AESCryptor::ReadResult AESCryptor::attempt_read(FileDesc fd, SizeType pos, char* dst, IVLookupMode iv_mode,
473
                                                uint32_t& iv_out, Hmac& hmac)
474
{
407,298✔
475
    IVTable& iv = get_iv_table(fd, pos, iv_mode);
407,298✔
476
    iv_out = iv.iv1;
407,298✔
477
    if (iv.iv1 == 0) {
407,298✔
478
        hmac.fill(0);
61,977✔
479
        return ReadResult::Uninitialized;
61,977✔
480
    }
61,977✔
481

482
    size_t actual = check_read(fd, data_pos_to_file_pos(pos), m_rw_buffer.get());
345,321✔
483
    if (actual < encryption_page_size) {
345,321✔
484
        return ReadResult::Eof;
6✔
485
    }
6✔
486

487
    calculate_hmac(hmac);
345,315✔
488
    if (!constant_time_equals(hmac, iv.hmac1)) {
345,315✔
489
        // Either the DB is corrupted or we were interrupted between writing the
490
        // new IV and writing the data
491
        if (iv.iv2 == 0) {
2,886✔
492
            return ReadResult::InterruptedFirstWrite;
2,796✔
493
        }
2,796✔
494

495
        if (constant_time_equals(hmac, iv.hmac2)) {
90✔
496
            // Un-bump the IV since the write with the bumped IV never actually
497
            // happened
498
            memcpy(&iv.iv1, &iv.iv2, 32);
3✔
499
        }
3✔
500
        else {
87✔
501
            // If the file has been shrunk and then re-expanded, we may have
502
            // old hmacs that don't go with this data. ftruncate() is
503
            // required to fill any added space with zeroes, so assume that's
504
            // what happened if the buffer is all zeroes
505
            bool all_zero = std::all_of(&m_rw_buffer[0], &m_rw_buffer[actual], [](char c) {
49,227✔
506
                return c == 0;
49,227✔
507
            });
49,227✔
508
            if (all_zero)
87✔
509
                return ReadResult::StaleHmac;
12✔
510
            return ReadResult::Failed;
75✔
511
        }
87✔
512
    }
90✔
513

514
    // We may expect some address ranges of the destination buffer of
515
    // AESCryptor::read() to stay unmodified, i.e. being overwritten with
516
    // the same bytes as already present, and may have read-access to these
517
    // from other threads while decryption is taking place.
518
    //
519
    // However, some implementations of AES_cbc_encrypt(), in particular
520
    // OpenSSL, will put garbled bytes as an intermediate step during the
521
    // operation which will lead to incorrect data being read by other
522
    // readers concurrently accessing that page. Incorrect data leads to
523
    // crashes.
524
    //
525
    // We therefore decrypt to a temporary buffer first and then copy the
526
    // completely decrypted data after.
527
    crypt(mode_Decrypt, pos, m_dst_buffer.get(), m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
342,432✔
528
    memcpy_if_changed(dst, m_dst_buffer.get(), encryption_page_size);
342,432✔
529
    return ReadResult::Success;
342,432✔
530
}
345,315✔
531

532
void AESCryptor::try_read_block(FileDesc fd, SizeType pos, char* dst) noexcept
533
{
×
534
    size_t bytes_read = check_read(fd, data_pos_to_file_pos(pos), m_rw_buffer.get());
×
535

536
    if (bytes_read == 0) {
×
537
        std::cerr << "Read failed: 0x" << std::hex << pos << std::endl;
×
538
        memset(dst, 0x55, encryption_page_size);
×
539
        return;
×
540
    }
×
541

542
    IVTable& iv = get_iv_table(fd, pos, IVLookupMode::Refetch);
×
543
    if (iv.iv1 == 0) {
×
544
        std::cerr << "Block never written: 0x" << std::hex << pos << std::endl;
×
545
        memset(dst, 0xAA, encryption_page_size);
×
546
        return;
×
547
    }
×
548

549
    Hmac hmac;
×
550
    calculate_hmac(hmac);
×
551
    if (!constant_time_equals(hmac, iv.hmac1)) {
×
552
        if (iv.iv2 == 0) {
×
553
            std::cerr << "First write interrupted: 0x" << std::hex << pos << std::endl;
×
554
        }
×
555

556
        if (constant_time_equals(hmac, iv.hmac2)) {
×
557
            std::cerr << "Restore old IV: 0x" << std::hex << pos << std::endl;
×
558
            memcpy(&iv.iv1, &iv.iv2, 32);
×
559
        }
×
560
        else {
×
561
            std::cerr << "Checksum failed: 0x" << std::hex << pos << std::endl;
×
562
        }
×
563
    }
×
564
    crypt(mode_Decrypt, pos, dst, m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
×
565
}
×
566

567
void AESCryptor::write(FileDesc fd, SizeType pos, const char* src, WriteMarker* marker) noexcept
568
{
138,282✔
569
    IVTable& iv = get_iv_table(fd, pos);
138,282✔
570

571
    memcpy(&iv.iv2, &iv.iv1, 32); // this is also copying the hmac
138,282✔
572
    do {
138,318✔
573
        ++iv.iv1;
138,318✔
574
        // 0 is reserved for never-been-used, so bump if we just wrapped around
575
        if (iv.iv1 == 0)
138,318✔
576
            ++iv.iv1;
×
577

578
        crypt(mode_Encrypt, pos, m_rw_buffer.get(), src, reinterpret_cast<const char*>(&iv.iv1));
138,318✔
579
        hmac_sha224(Span(reinterpret_cast<uint8_t*>(m_rw_buffer.get()), encryption_page_size), iv.hmac1,
138,318✔
580
                    Span(m_key).sub_span<32>());
138,318✔
581
        // In the extremely unlikely case that both the old and new versions have
582
        // the same hash we won't know which IV to use, so bump the IV until
583
        // they're different.
584
    } while (REALM_UNLIKELY(iv.hmac1 == iv.hmac2));
138,318✔
585

586
    if (marker)
138,282✔
587
        marker->mark(pos);
633✔
588
    File::write_static(fd, iv_table_pos(pos), reinterpret_cast<const char*>(&iv), sizeof(iv));
138,282✔
589
    // FIXME: doesn't this need a barrier? The IV table is very likely to
590
    // make it to disk first due to being issued first and being earlier in
591
    // the file, but not guaranteed
592
    File::write_static(fd, data_pos_to_file_pos(pos), m_rw_buffer.get(), encryption_page_size);
138,282✔
593
    if (marker)
138,282✔
594
        marker->unmark();
633✔
595
    m_iv_buffer_cache[page_index(pos)] = iv;
138,282✔
596
}
138,282✔
597

598
void AESCryptor::crypt(EncryptionMode mode, SizeType pos, char* dst, const char* src, const char* stored_iv) noexcept
599
{
480,750✔
600
    uint8_t iv[aes_block_size] = {0};
480,750✔
601
    memcpy(iv, stored_iv, 4);
480,750✔
602
    memcpy(iv + 4, &pos, sizeof(pos));
480,750✔
603

604
#if REALM_PLATFORM_APPLE
605
    CCCryptorRef cryptor = mode == mode_Encrypt ? m_encr : m_decr;
606
    CCCryptorReset(cryptor, iv);
607

608
    size_t bytesEncrypted = 0;
609
    CCCryptorStatus err =
610
        CCCryptorUpdate(cryptor, src, encryption_page_size, dst, encryption_page_size, &bytesEncrypted);
611
    REALM_ASSERT(err == kCCSuccess);
612
    REALM_ASSERT(bytesEncrypted == encryption_page_size);
613
#elif defined(_WIN32)
614
    ULONG cbData;
615
    int i;
616

617
    if (mode == mode_Encrypt) {
618
        i = BCryptEncrypt(m_aes_key_handle, (PUCHAR)src, encryption_page_size, nullptr, (PUCHAR)iv, sizeof(iv),
619
                          (PUCHAR)dst, encryption_page_size, &cbData, 0);
620
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptEncrypt()", i);
621
        REALM_ASSERT_RELEASE_EX(cbData == encryption_page_size && "BCryptEncrypt()", cbData);
622
    }
623
    else if (mode == mode_Decrypt) {
624
        i = BCryptDecrypt(m_aes_key_handle, (PUCHAR)src, encryption_page_size, nullptr, (PUCHAR)iv, sizeof(iv),
625
                          (PUCHAR)dst, encryption_page_size, &cbData, 0);
626
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptDecrypt()", i);
627
        REALM_ASSERT_RELEASE_EX(cbData == encryption_page_size && "BCryptDecrypt()", cbData);
628
    }
629
    else {
630
        REALM_UNREACHABLE();
631
    }
632

633
#else
634
    if (!EVP_CipherInit_ex(m_ctx, EVP_aes_256_cbc(), NULL, m_key.data(), iv, mode))
480,750✔
635
        handle_error();
×
636

637
    int len;
480,750✔
638
    // Use zero padding - we always write a whole page
639
    EVP_CIPHER_CTX_set_padding(m_ctx, 0);
480,750✔
640

641
    if (!EVP_CipherUpdate(m_ctx, reinterpret_cast<uint8_t*>(dst), &len, reinterpret_cast<const uint8_t*>(src),
480,750✔
642
                          encryption_page_size))
480,750✔
643
        handle_error();
×
644

645
    // Finalize the encryption. Should not output further data.
646
    if (!EVP_CipherFinal_ex(m_ctx, reinterpret_cast<uint8_t*>(dst) + len, &len))
480,750✔
647
        handle_error();
×
648
#endif
480,750✔
649
}
480,750✔
650

651
EncryptedFile::EncryptedFile(const char* key, FileDesc fd)
652
    : fd(fd)
321✔
653
    , cryptor(key)
321✔
654
{
321✔
655
}
321✔
656

657
std::unique_ptr<EncryptedFileMapping> EncryptedFile::add_mapping(SizeType file_offset, void* addr, size_t size,
658
                                                                 File::AccessMode access)
659
{
10,974✔
660
    auto mapping = std::make_unique<EncryptedFileMapping>(*this, file_offset, addr, size, access);
10,974✔
661
    CheckedLockGuard lock(mutex);
10,974✔
662
    mappings.push_back(mapping.get());
10,974✔
663
    return mapping;
10,974✔
664
}
10,974✔
665

666
EncryptedFileMapping::EncryptedFileMapping(EncryptedFile& file, SizeType file_offset, void* addr, size_t size,
667
                                           File::AccessMode access, util::WriteObserver* observer,
668
                                           util::WriteMarker* marker)
669
    : m_file(file)
10,974✔
670
    , m_access(access)
10,974✔
671
    , m_observer(observer)
10,974✔
672
    , m_marker(marker)
10,974✔
673
#ifdef REALM_DEBUG
674
    , m_validate_buffer(new char[encryption_page_size])
10,974✔
675
#endif
676
{
10,974✔
677
    set(addr, size, file_offset); // throws
10,974✔
678
}
10,974✔
679

680
EncryptedFileMapping::~EncryptedFileMapping()
681
{
10,974✔
682
    CheckedLockGuard lock(m_file.mutex);
10,974✔
683
    for (auto& e : m_page_state) {
1,994,124✔
684
        REALM_ASSERT(is_not(e, Writable));
1,994,124✔
685
    }
1,994,124✔
686
    if (m_access == File::access_ReadWrite) {
10,974✔
687
        do_flush();
5,298✔
688
    }
5,298✔
689

690
    auto it = std::find(m_file.mappings.begin(), m_file.mappings.end(), this);
10,974✔
691
    REALM_ASSERT(it != m_file.mappings.end());
10,974✔
692
    if (it != m_file.mappings.end()) {
10,974✔
693
        m_file.mappings.erase(it);
10,974✔
694
    }
10,974✔
695
}
10,974✔
696

697
// offset within page, not within file
698
uint16_t EncryptedFileMapping::get_offset_of_address(const void* addr) const noexcept
699
{
×
700
    return reinterpret_cast<uintptr_t>(addr) & (encryption_page_size - 1);
×
701
}
×
702

703
size_t EncryptedFileMapping::get_local_index_of_address(const void* addr, size_t offset) const noexcept
704
{
7,870,326✔
705
    REALM_ASSERT_EX(addr >= m_addr, addr, m_addr);
7,870,326✔
706
    return (reinterpret_cast<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(m_addr) + offset) / encryption_page_size;
7,870,326✔
707
}
7,870,326✔
708

709
bool EncryptedFileMapping::contains_page(size_t block_in_file) const noexcept
710
{
502,290✔
711
    return block_in_file - m_first_page < m_page_state.size();
502,290✔
712
}
502,290✔
713

714
char* EncryptedFileMapping::page_addr(size_t local_ndx) const noexcept
715
{
1,753,422✔
716
    REALM_ASSERT_DEBUG(local_ndx < m_page_state.size());
1,753,422✔
717
    return static_cast<char*>(m_addr) + (local_ndx * encryption_page_size);
1,753,422✔
718
}
1,753,422✔
719

720
SizeType EncryptedFileMapping::page_pos(size_t local_ndx) const noexcept
721
{
540,696✔
722
    return SizeType(local_ndx + m_first_page) * encryption_page_size;
540,696✔
723
}
540,696✔
724

725
// If we have multiple mappings for the same part of the file, one of them may
726
// already contain the page we're about to read and if so we can skip reading
727
// it and instead just memcpy it.
728
bool EncryptedFileMapping::copy_up_to_date_page(size_t local_ndx) noexcept
729
{
202,743✔
730
    REALM_ASSERT_EX(local_ndx < m_page_state.size(), local_ndx, m_page_state.size());
202,743✔
731
    // Precondition: this method must never be called for a page which
732
    // is already up to date.
733
    REALM_ASSERT(is_not(m_page_state[local_ndx], UpToDate));
202,743✔
734
    size_t ndx_in_file = local_ndx + m_first_page;
202,743✔
735
    for (auto& m : m_file.mappings) {
305,964✔
736
        m->assert_locked();
305,964✔
737
        if (m == this || !m->contains_page(ndx_in_file))
305,964✔
738
            continue;
297,765✔
739

740
        size_t other_mapping_ndx = ndx_in_file - m->m_first_page;
8,199✔
741
        auto other_state = m->m_page_state[other_mapping_ndx];
8,199✔
742
        if (is(other_state, Writable) || is_not(other_state, UpToDate))
8,199✔
743
            continue;
3,930✔
744

745
        memcpy_if_changed(page_addr(local_ndx), m->page_addr(other_mapping_ndx), encryption_page_size);
4,269✔
746
        set(m_page_state[local_ndx], UpToDate);
4,269✔
747
        clear(m_page_state[local_ndx], StaleIV);
4,269✔
748
        return true;
4,269✔
749
    }
8,199✔
750
    return false;
198,474✔
751
}
202,743✔
752

753
// Whenever we advance our reader view of the file we mark all previously
754
// up-to-date pages as being possibly stale. On the next access of the page we
755
// then check if the IV for that page has changed to determine if the page has
756
// actually changed or if we can just mark it as being up-to-date again.
757
bool EncryptedFileMapping::check_possibly_stale_page(size_t local_ndx) noexcept
758
{
198,474✔
759
    if (is_not(m_page_state[local_ndx], StaleIV))
198,474✔
760
        return false;
130,053✔
761
    size_t ndx_in_file = local_ndx + m_first_page;
68,421✔
762
    bool did_change = m_file.cryptor.refresh_iv(m_file.fd, ndx_in_file);
68,421✔
763
    // Update the page state in all mappings and not just the current one because
764
    // refresh_iv() only returns true once per page per write. Deferring this
765
    // until copy_up_to_date_page() almost works, but this mapping could be
766
    // removed before the other mapping copies the page.
767
    for (auto& m : m_file.mappings) {
68,433✔
768
        m->assert_locked();
68,433✔
769
        if (!m->contains_page(ndx_in_file))
68,433✔
770
            continue;
12✔
771
        auto& state = m->m_page_state[ndx_in_file - m->m_first_page];
68,421✔
772
        if (is(state, StaleIV)) {
68,421✔
773
            REALM_ASSERT(is_not(state, UpToDate));
68,421✔
774
            clear(state, StaleIV);
68,421✔
775
            if (!did_change)
68,421✔
776
                set(state, UpToDate);
3,006✔
777
        }
68,421✔
778
    }
68,421✔
779
    return !did_change;
68,421✔
780
}
198,474✔
781

782
REALM_NORETURN
783
REALM_COLD
784
void EncryptedFileMapping::throw_decryption_error(size_t local_ndx, std::string_view msg)
785
{
231✔
786
    size_t fs = to_size_t(File::get_size_static(m_file.fd));
231✔
787
    throw DecryptionFailed(util::format("page %1 in file of size %2 %3", local_ndx + m_first_page, fs, msg));
231✔
788
}
231✔
789

790
void EncryptedFileMapping::refresh_page(size_t local_ndx, bool to_modify)
791
{
202,743✔
792
    REALM_ASSERT_EX(local_ndx < m_page_state.size(), local_ndx, m_page_state.size());
202,743✔
793
    REALM_ASSERT(is_not(m_page_state[local_ndx], Dirty));
202,743✔
794
    REALM_ASSERT(is_not(m_page_state[local_ndx], Writable));
202,743✔
795
    if (copy_up_to_date_page(local_ndx) || check_possibly_stale_page(local_ndx)) {
202,743✔
796
        return;
7,275✔
797
    }
7,275✔
798

799
    char* addr = page_addr(local_ndx);
195,468✔
800
    switch (m_file.cryptor.read(m_file.fd, page_pos(local_ndx), addr, m_observer)) {
195,468✔
801
        case AESCryptor::ReadResult::Eof:
6✔
802
            if (!to_modify)
6✔
803
                throw_decryption_error(local_ndx, "is out of bounds");
6✔
804
            break;
6✔
805
        case AESCryptor::ReadResult::Uninitialized:
61,977✔
806
            if (!to_modify)
61,977✔
807
                throw_decryption_error(local_ndx, "has never been written to");
195✔
808
            break;
61,977✔
809
        case AESCryptor::ReadResult::InterruptedFirstWrite:
105✔
810
            if (!to_modify)
105✔
811
                throw_decryption_error(local_ndx, "has never been successfully written to, but a write was begun");
9✔
812
            break;
105✔
813
        case AESCryptor::ReadResult::StaleHmac:
12✔
814
            break;
12✔
815
        case AESCryptor::ReadResult::Failed:
21✔
816
            throw_decryption_error(
21✔
817
                local_ndx, "failed the HMAC check. Either the encryption key is incorrect or data is corrupted");
21✔
818
        case AESCryptor::ReadResult::Success:
133,368✔
819
            break;
133,368✔
820
    }
195,468✔
821
    set(m_page_state[local_ndx], UpToDate);
195,237✔
822
}
195,237✔
823

824
void EncryptedFile::mark_data_as_possibly_stale()
825
{
71,628✔
826

827
    util::CheckedLockGuard lock(mutex);
71,628✔
828
    cryptor.invalidate_ivs();
71,628✔
829
    for (auto& m : mappings) {
71,640✔
830
        m->assert_locked();
71,640✔
831
        m->mark_pages_for_iv_check();
71,640✔
832
    }
71,640✔
833
}
71,628✔
834

835
void EncryptedFileMapping::mark_pages_for_iv_check()
836
{
71,640✔
837
    for (auto& state : m_page_state) {
18,325,776✔
838
        if (is(state, UpToDate) && is_not(state, Dirty | Writable)) {
18,325,776✔
839
            REALM_ASSERT(is_not(state, StaleIV));
74,241✔
840
            clear(state, UpToDate);
74,241✔
841
            set(state, StaleIV);
74,241✔
842
        }
74,241✔
843
    }
18,325,776✔
844
}
71,640✔
845

846
void EncryptedFileMapping::write_and_update_all(size_t local_ndx, uint16_t offset, uint16_t size) noexcept
847
{
1,110,294✔
848
    REALM_ASSERT(is(m_page_state[local_ndx], Writable));
1,110,294✔
849
    REALM_ASSERT(is(m_page_state[local_ndx], UpToDate));
1,110,294✔
850
    REALM_ASSERT(is_not(m_page_state[local_ndx], StaleIV));
1,110,294✔
851
    REALM_ASSERT(offset + size <= encryption_page_size);
1,110,294✔
852
    // Go through all other mappings of this file and copy changes into those mappings
853
    size_t ndx_in_file = local_ndx + m_first_page;
1,110,294✔
854
    for (auto& m : m_file.mappings) {
1,276,332✔
855
        m->assert_locked();
1,276,332✔
856
        if (m == this || !m->contains_page(ndx_in_file))
1,276,332✔
857
            continue;
1,161,267✔
858

859
        size_t other_local_ndx = ndx_in_file - m->m_first_page;
115,065✔
860
        auto& state = m->m_page_state[other_local_ndx];
115,065✔
861
        if (is(state, UpToDate)) {
115,065✔
862
            memcpy_if_changed(m->page_addr(other_local_ndx) + offset, page_addr(local_ndx) + offset, size);
72,198✔
863
        }
72,198✔
864
        // If the target page is possibly stale then we need to copy the entire
865
        // page and not just the bytes we just touched as other parts of the
866
        // page may be out of date
867
        else if (is(state, StaleIV)) {
42,867✔
868
            memcpy_if_changed(m->page_addr(other_local_ndx), page_addr(local_ndx), encryption_page_size);
60✔
869
            set(state, UpToDate);
60✔
870
            clear(state, StaleIV);
60✔
871
        }
60✔
872
    }
115,065✔
873
    set(m_page_state[local_ndx], Dirty);
1,110,294✔
874
    clear(m_page_state[local_ndx], Writable);
1,110,294✔
875
}
1,110,294✔
876

877
void EncryptedFileMapping::validate_page(size_t local_ndx) noexcept
878
{
16,640,628✔
879
#ifdef REALM_DEBUG
16,640,628✔
880
    REALM_ASSERT(local_ndx < m_page_state.size());
16,640,628✔
881
    if (is_not(m_page_state[local_ndx], UpToDate))
16,640,628✔
882
        return;
16,431,942✔
883

884
    switch (m_file.cryptor.read(m_file.fd, page_pos(local_ndx), m_validate_buffer.get(), m_observer)) {
208,686✔
885
        case AESCryptor::ReadResult::Eof:
✔
886
        case AESCryptor::ReadResult::Uninitialized:
✔
887
        case AESCryptor::ReadResult::InterruptedFirstWrite:
✔
888
        case AESCryptor::ReadResult::StaleHmac:
✔
889
            return;
×
890
        case AESCryptor::ReadResult::Failed:
✔
891
            abort();
×
892
        case AESCryptor::ReadResult::Success:
208,692✔
893
            break;
208,692✔
894
    }
208,686✔
895

896
    const size_t ndx_in_file = local_ndx + m_first_page;
208,692✔
897
    for (auto& m : m_file.mappings) {
367,092✔
898
        m->assert_locked();
367,092✔
899
        size_t other_local_ndx = ndx_in_file - m->m_first_page;
367,092✔
900
        if (m != this && m->contains_page(ndx_in_file) && is(m->m_page_state[other_local_ndx], Dirty)) {
367,092✔
901
            memcpy(m_validate_buffer.get(), m->page_addr(other_local_ndx), encryption_page_size);
1,920✔
902
            break;
1,920✔
903
        }
1,920✔
904
    }
367,092✔
905

906
    if (memcmp(m_validate_buffer.get(), page_addr(local_ndx), encryption_page_size) != 0) {
208,692✔
907
        util::format(std::cerr, "mismatch %1: fd(%2) page(%3/%4) %5 %6\n", this, m_file.fd, local_ndx,
×
908
                     m_page_state.size(), m_validate_buffer.get(), page_addr(local_ndx));
×
909
        REALM_TERMINATE("");
910
    }
×
911
#else
912
    static_cast<void>(local_ndx);
913
#endif
914
}
208,692✔
915

916
void EncryptedFileMapping::validate() noexcept
917
{
49,254✔
918
#ifdef REALM_DEBUG
49,254✔
919
    for (size_t i = 0; i < m_page_state.size(); ++i)
8,418,174✔
920
        validate_page(i);
8,368,920✔
921
#endif
49,254✔
922
}
49,254✔
923

924
void EncryptedFileMapping::do_flush(bool skip_validate) noexcept
925
{
88,581✔
926
    for (size_t i = 0; i < m_page_state.size(); ++i) {
22,552,971✔
927
        if (is_not(m_page_state[i], Dirty)) {
22,464,390✔
928
            if (!skip_validate) {
22,327,854✔
929
                validate_page(i);
8,271,711✔
930
            }
8,271,711✔
931
            continue;
22,327,854✔
932
        }
22,327,854✔
933
        m_file.cryptor.write(m_file.fd, page_pos(i), page_addr(i), m_marker);
136,536✔
934
        clear(m_page_state[i], Dirty);
136,536✔
935
    }
136,536✔
936

937
    // some of the tests call flush() on very small writes which results in
938
    // validating on every flush being unreasonably slow
939
    if (!skip_validate) {
88,581✔
940
        validate();
49,254✔
941
    }
49,254✔
942
}
88,581✔
943

944
void EncryptedFileMapping::flush(bool skip_validate) noexcept
945
{
71,406✔
946
    util::CheckedLockGuard lock(m_file.mutex);
71,406✔
947
    do_flush(skip_validate);
71,406✔
948
}
71,406✔
949

950
void EncryptedFileMapping::sync() noexcept
951
{
903✔
952
    util::CheckedLockGuard lock(m_file.mutex);
903✔
953
    do_sync();
903✔
954
}
903✔
955

956
#ifdef _MSC_VER
957
#pragma warning(disable : 4297) // throw in noexcept
958
#endif
959
void EncryptedFileMapping::do_sync() noexcept
960
{
903✔
961
    do_flush();
903✔
962

963
#ifdef _WIN32
964
    if (FlushFileBuffers(m_file.fd))
965
        return;
966
    throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
967
#else
968
    fsync(m_file.fd);
903✔
969
#endif
903✔
970
}
903✔
971
#ifdef _MSC_VER
972
#pragma warning(default : 4297)
973
#endif
974

975
void EncryptedFileMapping::write_barrier(const void* addr, size_t size) noexcept
976
{
1,057,857✔
977
    CheckedLockGuard lock(m_file.mutex);
1,057,857✔
978
    REALM_ASSERT(size > 0);
1,057,857✔
979
    REALM_ASSERT(m_access == File::access_ReadWrite);
1,057,857✔
980

981
    size_t local_ndx = get_local_index_of_address(addr);
1,057,857✔
982
    auto offset_in_page = uint16_t(static_cast<const char*>(addr) - page_addr(local_ndx));
1,057,857✔
983
    size += offset_in_page;
1,057,857✔
984

985
    // Propagate changes to all other decrypted pages mapping the same memory
986
    while (size > 0) {
2,168,160✔
987
        REALM_ASSERT(local_ndx < m_page_state.size());
1,110,303✔
988
        REALM_ASSERT(is(m_page_state[local_ndx], PageState::Writable));
1,110,303✔
989
        auto bytes_in_page = uint16_t(std::min<size_t>(encryption_page_size, size) - offset_in_page);
1,110,303✔
990
        write_and_update_all(local_ndx, offset_in_page, bytes_in_page);
1,110,303✔
991
        size -= offset_in_page + bytes_in_page;
1,110,303✔
992
        offset_in_page = 0;
1,110,303✔
993
        ++local_ndx;
1,110,303✔
994
    }
1,110,303✔
995
}
1,057,857✔
996

997
void EncryptedFileMapping::read_barrier(const void* addr, size_t size, bool to_modify)
998
{
3,406,182✔
999
    CheckedLockGuard lock(m_file.mutex);
3,406,182✔
1000
    REALM_ASSERT(size > 0);
3,406,182✔
1001
    size_t begin = get_local_index_of_address(addr);
3,406,182✔
1002
    size_t end = get_local_index_of_address(addr, size - 1);
3,406,182✔
1003
    for (size_t local_ndx = begin; local_ndx <= end; ++local_ndx) {
7,071,879✔
1004
        PageState& ps = m_page_state[local_ndx];
3,665,697✔
1005
        if (is_not(ps, UpToDate))
3,665,697✔
1006
            refresh_page(local_ndx, to_modify);
202,743✔
1007
        if (to_modify)
3,665,697✔
1008
            set(ps, Writable);
1,110,303✔
1009
    }
3,665,697✔
1010
}
3,406,182✔
1011

1012
void EncryptedFileMapping::extend_to(SizeType offset, size_t new_size)
1013
{
189✔
1014
    CheckedLockGuard lock(m_file.mutex);
189✔
1015
    REALM_ASSERT_EX(new_size % encryption_page_size == 0, new_size, encryption_page_size);
189✔
1016
    m_page_state.resize(page_count(new_size), PageState::Clean);
189✔
1017
    m_file.cryptor.set_data_size(offset + SizeType(new_size));
189✔
1018
}
189✔
1019

1020
void EncryptedFileMapping::set(void* new_addr, size_t new_size, SizeType new_file_offset)
1021
{
10,974✔
1022
    CheckedLockGuard lock(m_file.mutex);
10,974✔
1023
    REALM_ASSERT(new_file_offset % encryption_page_size == 0);
10,974✔
1024
    REALM_ASSERT(new_size % encryption_page_size == 0);
10,974✔
1025

1026
    // This seems dangerous - correct operation in a setting with multiple (partial)
1027
    // mappings of the same file would rely on ordering of individual mapping requests.
1028
    // Currently we only ever extend the file - but when we implement continuous defrag,
1029
    // this design should be revisited.
1030
    m_file.cryptor.set_data_size(new_file_offset + SizeType(new_size));
10,974✔
1031

1032
    do_flush();
10,974✔
1033
    m_addr = new_addr;
10,974✔
1034

1035
    // set_data_size() would have thrown if this cast would overflow
1036
    m_first_page = size_t(new_file_offset / encryption_page_size);
10,974✔
1037
    m_page_state.clear();
10,974✔
1038
    m_page_state.resize(new_size / encryption_page_size, PageState::Clean);
10,974✔
1039
}
10,974✔
1040

1041
SizeType encrypted_size_to_data_size(SizeType size) noexcept
1042
{
2,493✔
1043
    return size == 0 ? 0 : file_pos_to_data_pos(size);
2,493✔
1044
}
2,493✔
1045

1046
SizeType data_size_to_encrypted_size(SizeType size) noexcept
1047
{
414✔
1048
    SizeType r = size % encryption_page_size;
414✔
1049
    size += r ? encryption_page_size - r : 0;
414✔
1050
    return data_pos_to_file_pos(size);
414✔
1051
}
414✔
1052
} // namespace realm::util
1053
#else
1054

1055
namespace realm::util {
1056
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1057
{
1058
    return size;
1059
}
1060

1061
File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept
1062
{
1063
    return size;
1064
}
1065
} // namespace realm::util
1066
#endif // REALM_ENABLE_ENCRYPTION
1067

1068
namespace realm::util {
1069
std::string DecryptionFailed::get_message_with_bt(std::string_view msg)
1070
{
231✔
1071
    auto bt = Backtrace::capture();
231✔
1072
    std::stringstream ss;
231✔
1073
    bt.print(ss);
231✔
1074
    return util::format("Decryption failed: %1\n%2\n", msg, ss.str());
231✔
1075
}
231✔
1076
} // namespace realm::util
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

© 2026 Coveralls, Inc