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

realm / realm-core / 2267

27 Apr 2024 02:45AM UTC coverage: 90.734% (-0.02%) from 90.756%
2267

push

Evergreen

web-flow
Merge pull request #7639 from realm/release/14.6.0-again

merge release 14.6.0

101938 of 180236 branches covered (56.56%)

211 of 254 new or added lines in 5 files covered. (83.07%)

94 existing lines in 16 files now uncovered.

212406 of 234097 relevant lines covered (90.73%)

5753046.69 hits per line

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

80.95
/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/file_mapper.hpp>
22

23
#include <sstream>
24

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

32
#include <cstdlib>
33
#include <algorithm>
34
#include <chrono>
35
#include <stdexcept>
36
#include <string_view>
37
#include <system_error>
38
#include <thread>
39

40
#ifdef REALM_DEBUG
41
#include <cstdio>
42
#endif
43

44
#include <array>
45
#include <cstring>
46
#include <iostream>
47

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

58
namespace realm::util {
59
SharedFileInfo::SharedFileInfo(const uint8_t* key)
60
    : cryptor(key)
4,572✔
61
{
8,616✔
62
}
8,616✔
63

64
// We have the following constraints here:
65
//
66
// 1. When writing, we only know which 4k page is dirty, and not what bytes
67
//    within the page are dirty, so we always have to write in 4k blocks.
68
// 2. Pages being written need to be entirely within an 8k-aligned block to
69
//    ensure that they're written to the hardware in atomic blocks.
70
// 3. We need to store the IV used for each 4k page somewhere, so that we can
71
//    ensure that we never reuse an IV (and still be decryptable).
72
//
73
// Because pages need to be aligned, we can't just prepend the IV to each page,
74
// or we'd have to double the size of the file (as the rest of the 4k block
75
// containing the IV would not be usable). Writing the IVs to a different part
76
// of the file from the data results in them not being in the same 8k block, and
77
// so it is possible that only the IV or only the data actually gets updated on
78
// disk. We deal with this by storing four pieces of data about each page: the
79
// hash of the encrypted data, the current IV, the hash of the previous encrypted
80
// data, and the previous IV. To write, we encrypt the data, hash the ciphertext,
81
// then write the new IV/ciphertext hash, fsync(), and then write the new
82
// ciphertext. This ensures that if an error occurs between writing the IV and
83
// the ciphertext, we can still determine that we should use the old IV, since
84
// the ciphertext's hash will match the old ciphertext.
85

86
// This produces a file on disk with the following layout:
87
// 4k block of metadata   (up to 64 iv_table instances stored here)
88
// 64 * 4k blocks of data (up to 262144 bytes of data are stored here)
89
// 4k block of metadata
90
// 64 * 4k blocks of data
91
// ...
92

93
struct iv_table {
94
    uint32_t iv1 = 0;
95
    std::array<uint8_t, 28> hmac1 = {};
96
    uint32_t iv2 = 0;
97
    std::array<uint8_t, 28> hmac2 = {};
98
    bool operator==(const iv_table& other) const
99
    {
60,948✔
100
        return iv1 == other.iv1 && iv2 == other.iv2 && hmac1 == other.hmac1 && hmac2 == other.hmac2;
60,948✔
101
    }
60,948✔
102
    bool operator!=(const iv_table& other) const
103
    {
38,469✔
104
        return !(*this == other);
38,469✔
105
    }
38,469✔
106
};
107

108
namespace {
109
const int aes_block_size = 16;
110
const size_t block_size = 4096;
111

112
const size_t metadata_size = sizeof(iv_table);
113
const size_t blocks_per_metadata_block = block_size / metadata_size;
114
static_assert(metadata_size == 64,
115
              "changing the size of the metadata breaks compatibility with existing Realm files");
116

117
// map an offset in the data to the actual location in the file
118
template <typename Int>
119
Int real_offset(Int pos)
120
{
692,352✔
121
    REALM_ASSERT(pos >= 0);
692,352✔
122
    const size_t index = static_cast<size_t>(pos) / block_size;
692,352✔
123
    const size_t metadata_page_count = index / blocks_per_metadata_block + 1;
692,352✔
124
    return Int(pos + metadata_page_count * block_size);
692,352✔
125
}
692,352✔
126

127
// map a location in the file to the offset in the data
128
template <typename Int>
129
Int fake_offset(Int pos)
130
{
16,335✔
131
    REALM_ASSERT(pos >= 0);
16,335✔
132
    const size_t index = static_cast<size_t>(pos) / block_size;
16,335✔
133
    const size_t metadata_page_count = (index + blocks_per_metadata_block) / (blocks_per_metadata_block + 1);
16,335✔
134
    return pos - metadata_page_count * block_size;
16,335✔
135
}
16,335✔
136

137
// get the location of the iv_table for the given data (not file) position
138
off_t iv_table_pos(off_t pos)
139
{
136,776✔
140
    REALM_ASSERT(pos >= 0);
136,776✔
141
    const size_t index = static_cast<size_t>(pos) / block_size;
136,776✔
142
    const size_t metadata_block = index / blocks_per_metadata_block;
136,776✔
143
    const size_t metadata_index = index & (blocks_per_metadata_block - 1);
136,776✔
144
    return off_t(metadata_block * (blocks_per_metadata_block + 1) * block_size + metadata_index * metadata_size);
136,776✔
145
}
136,776✔
146

147
void check_write(FileDesc fd, off_t pos, const void* data, size_t len)
148
{
238,764✔
149
    uint64_t orig = File::get_file_pos(fd);
238,764✔
150
    File::seek_static(fd, pos);
238,764✔
151
    File::write_static(fd, static_cast<const char*>(data), len);
238,764✔
152
    File::seek_static(fd, orig);
238,764✔
153
}
238,764✔
154

155
size_t check_read(FileDesc fd, off_t pos, void* dst, size_t len)
156
{
587,850✔
157
    uint64_t orig = File::get_file_pos(fd);
587,850✔
158
    File::seek_static(fd, pos);
587,850✔
159
    size_t ret = File::read_static(fd, static_cast<char*>(dst), len);
587,850✔
160
    File::seek_static(fd, orig);
587,850✔
161
    return ret;
587,850✔
162
}
587,850✔
163

164
} // anonymous namespace
165

166
// first block is iv data, second page is data
167
static_assert(c_min_encrypted_file_size == 2 * block_size,
168
              "chaging the block size breaks encrypted file portability");
169

170
AESCryptor::AESCryptor(const uint8_t* key)
171
    : m_rw_buffer(new char[block_size])
4,620✔
172
    , m_dst_buffer(new char[block_size])
4,620✔
173
{
8,712✔
174
    memcpy(m_aesKey.data(), key, 32);
8,712✔
175
    memcpy(m_hmacKey.data(), key + 32, 32);
8,712✔
176

177
#if REALM_PLATFORM_APPLE
4,092✔
178
    // A random iv is passed to CCCryptorReset. This iv is *not used* by Realm; we set it manually prior to
179
    // each call to BCryptEncrypt() and BCryptDecrypt(). We pass this random iv as an attempt to
180
    // suppress a false encryption security warning from the IBM Bluemix Security Analyzer (PR[#2911])
181
    unsigned char u_iv[kCCKeySizeAES256];
4,092✔
182
    arc4random_buf(u_iv, kCCKeySizeAES256);
4,092✔
183
    void* iv = u_iv;
4,092✔
184
    CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_encr);
4,092✔
185
    CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_decr);
4,092✔
186
#elif defined(_WIN32)
187
    BCRYPT_ALG_HANDLE hAesAlg = NULL;
188
    int ret;
189
    ret = BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
190
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptOpenAlgorithmProvider()", ret);
191

192
    ret = BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC,
193
                            sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
194
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptSetProperty()", ret);
195

196
    ret = BCryptGenerateSymmetricKey(hAesAlg, &m_aes_key_handle, nullptr, 0, (PBYTE)key, 32, 0);
197
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptGenerateSymmetricKey()", ret);
198
#else
199
    m_ctx = EVP_CIPHER_CTX_new();
4,620✔
200
    if (!m_ctx)
4,620✔
201
        handle_error();
202
#endif
4,620✔
203
}
8,712✔
204

205
AESCryptor::~AESCryptor() noexcept
206
{
8,712✔
207
#if REALM_PLATFORM_APPLE
4,092✔
208
    CCCryptorRelease(m_encr);
4,092✔
209
    CCCryptorRelease(m_decr);
4,092✔
210
#elif defined(_WIN32)
211
#else
212
    EVP_CIPHER_CTX_cleanup(m_ctx);
4,620✔
213
    EVP_CIPHER_CTX_free(m_ctx);
4,620✔
214
#endif
4,620✔
215
}
8,712✔
216

217
void AESCryptor::check_key(const uint8_t* key)
218
{
6,198✔
219
    if (memcmp(m_aesKey.data(), key, 32) != 0 || memcmp(m_hmacKey.data(), key + 32, 32) != 0)
6,198✔
220
        throw DecryptionFailed();
6✔
221
}
6,198✔
222

223
void AESCryptor::handle_error()
224
{
×
225
    throw std::runtime_error("Error occurred in encryption layer");
×
UNCOV
226
}
×
227

228
void AESCryptor::set_file_size(off_t new_size)
229
{
18,909✔
230
    REALM_ASSERT(new_size >= 0 && !int_cast_has_overflow<size_t>(new_size));
18,909✔
231
    size_t new_size_casted = size_t(new_size);
18,909✔
232
    size_t block_count = (new_size_casted + block_size - 1) / block_size;
18,909✔
233
    m_iv_buffer.reserve((block_count + blocks_per_metadata_block - 1) & ~(blocks_per_metadata_block - 1));
18,909✔
234
    m_iv_buffer_cache.reserve(m_iv_buffer.capacity());
18,909✔
235
}
18,909✔
236

237
iv_table& AESCryptor::get_iv_table(FileDesc fd, off_t data_pos, IVLookupMode mode) noexcept
238
{
691,782✔
239
    REALM_ASSERT(!int_cast_has_overflow<size_t>(data_pos));
691,782✔
240
    size_t data_pos_casted = size_t(data_pos);
691,782✔
241
    size_t idx = data_pos_casted / block_size;
691,782✔
242
    if (mode == IVLookupMode::UseCache && idx < m_iv_buffer.size())
691,782✔
243
        return m_iv_buffer[idx];
674,544✔
244

245
    size_t block_start = std::min(m_iv_buffer.size(), (idx / blocks_per_metadata_block) * blocks_per_metadata_block);
17,238✔
246
    size_t block_end = 1 + idx / blocks_per_metadata_block;
17,238✔
247
    REALM_ASSERT(block_end * blocks_per_metadata_block <= m_iv_buffer.capacity()); // not safe to allocate here
17,238✔
248
    if (block_end * blocks_per_metadata_block > m_iv_buffer.size()) {
17,238✔
249
        m_iv_buffer.resize(block_end * blocks_per_metadata_block);
11,856✔
250
        m_iv_buffer_cache.resize(m_iv_buffer.size());
11,856✔
251
    }
11,856✔
252

253
    for (size_t i = block_start; i < block_end * blocks_per_metadata_block; i += blocks_per_metadata_block) {
34,572✔
254
        off_t iv_pos = iv_table_pos(off_t(i * block_size));
17,394✔
255
        size_t bytes = check_read(fd, iv_pos, &m_iv_buffer[i], block_size);
17,394✔
256
        if (bytes < block_size)
17,394✔
257
            break; // rest is zero-filled by resize()
60✔
258
    }
17,394✔
259

260
    return m_iv_buffer[idx];
17,238✔
261
}
691,782✔
262

263
bool AESCryptor::check_hmac(const void* src, size_t len, const std::array<uint8_t, 28>& hmac) const
264
{
503,517✔
265
    std::array<uint8_t, 224 / 8> buffer;
503,517✔
266
    hmac_sha224(Span(reinterpret_cast<const uint8_t*>(src), len), buffer, m_hmacKey);
503,517✔
267

268
    // Constant-time memcmp to avoid timing attacks
269
    uint8_t result = 0;
503,517✔
270
    for (size_t i = 0; i < 224 / 8; ++i)
14,601,993✔
271
        result |= buffer[i] ^ hmac[i];
14,098,476✔
272
    return result == 0;
503,517✔
273
}
503,517✔
274

275
util::FlatMap<size_t, IVRefreshState>
276
AESCryptor::refresh_ivs(FileDesc fd, off_t data_pos, size_t page_ndx_in_file_expected, size_t end_page_ndx_in_file)
277
{
4,716✔
278
    REALM_ASSERT_EX(page_ndx_in_file_expected < end_page_ndx_in_file, page_ndx_in_file_expected,
4,716✔
279
                    end_page_ndx_in_file);
4,716✔
280
    // the indices returned are page indices, not block indices
281
    util::FlatMap<size_t, IVRefreshState> page_states;
4,716✔
282

283
    REALM_ASSERT(!int_cast_has_overflow<size_t>(data_pos));
4,716✔
284
    size_t data_pos_casted = size_t(data_pos);
4,716✔
285
    // the call to get_iv_table() below reads in all ivs in a chunk with size = blocks_per_metadata_block
286
    // so we will know if any iv in this chunk has changed
287
    const size_t block_ndx_refresh_start =
4,716✔
288
        ((data_pos_casted / block_size) / blocks_per_metadata_block) * blocks_per_metadata_block;
4,716✔
289
    const size_t block_ndx_refresh_end = block_ndx_refresh_start + blocks_per_metadata_block;
4,716✔
290
    REALM_ASSERT_EX(block_ndx_refresh_end <= m_iv_buffer.size(), block_ndx_refresh_start, block_ndx_refresh_end,
4,716✔
291
                    m_iv_buffer.size());
4,716✔
292

293
    get_iv_table(fd, data_pos, IVLookupMode::Refetch);
4,716✔
294

295
    size_t number_of_identical_blocks = 0;
4,716✔
296
    size_t last_page_index = -1;
4,716✔
297
    constexpr iv_table uninitialized_iv = {};
4,716✔
298
    // there may be multiple iv blocks per page so all must be unchanged for a page
299
    // to be considered unchanged. If any one of the ivs has changed then the entire page
300
    // must be refreshed. Eg. with a page_size() of 16k and block_size of 4k, if any of
301
    // the 4 ivs in that page are different, the entire page must be refreshed.
302
    const size_t num_required_identical_blocks_for_page_match = page_size() / block_size;
4,716✔
303
    for (size_t block_ndx = block_ndx_refresh_start; block_ndx < block_ndx_refresh_end; ++block_ndx) {
43,185✔
304
        size_t page_index = block_ndx * block_size / page_size();
43,023✔
305
        if (page_index >= end_page_ndx_in_file) {
43,023✔
306
            break;
4,554✔
307
        }
4,554✔
308
        if (page_index != last_page_index) {
38,469✔
309
            number_of_identical_blocks = 0;
20,604✔
310
        }
20,604✔
311
        if (m_iv_buffer_cache[block_ndx] != m_iv_buffer[block_ndx] || m_iv_buffer[block_ndx] == uninitialized_iv) {
38,469✔
312
            page_states[page_index] = IVRefreshState::RequiresRefresh;
27,789✔
313
            m_iv_buffer_cache[block_ndx] = m_iv_buffer[block_ndx];
27,789✔
314
        }
27,789✔
315
        else {
10,680✔
316
            ++number_of_identical_blocks;
10,680✔
317
        }
10,680✔
318
        if (number_of_identical_blocks >= num_required_identical_blocks_for_page_match) {
38,469✔
319
            REALM_ASSERT_EX(page_states.count(page_index) == 0, page_index, page_ndx_in_file_expected);
6,594✔
320
            page_states[page_index] = IVRefreshState::UpToDate;
6,594✔
321
        }
6,594✔
322
        last_page_index = page_index;
38,469✔
323
    }
38,469✔
324
    REALM_ASSERT_EX(page_states.count(page_ndx_in_file_expected) == 1, page_states.size(), page_ndx_in_file_expected,
4,716✔
325
                    block_ndx_refresh_start, blocks_per_metadata_block);
4,716✔
326
    return page_states;
4,716✔
327
}
4,716✔
328

329
size_t AESCryptor::read(FileDesc fd, off_t pos, char* dst, size_t size, WriteObserver* observer)
330
{
363,642✔
331
    REALM_ASSERT_EX(size % block_size == 0, size, block_size);
363,642✔
332
    // We need to throw DecryptionFailed if the key is incorrect or there has been a corruption in the data but
333
    // not in a reader starvation scenario where a different process is writing pages and ivs faster than we can read
334
    // them. We also want to optimize for a single process writer since in that case all the cached ivs are correct.
335
    // To do this, we first attempt to use the cached IV, and if it is invalid, read from disk again. During reader
336
    // starvation, the just read IV could already be out of date with the data page, so continue trying to read until
337
    // a match is found (for up to 5 seconds before giving up entirely).
338
    size_t retry_count = 0;
363,642✔
339
    std::pair<iv_table, size_t> last_iv_and_data_hash;
363,642✔
340
    auto retry_start_time = std::chrono::steady_clock::now();
363,642✔
341
    size_t num_identical_reads = 1;
363,642✔
342
    auto retry = [&](std::string_view page_data, const iv_table& iv, const char* debug_from) {
363,642✔
343
        constexpr auto max_retry_period = std::chrono::seconds(5);
711✔
344
        auto elapsed = std::chrono::steady_clock::now() - retry_start_time;
711✔
345
        bool we_are_alone = true;
711✔
346
        // not having an observer set means that we're alone. (or should mean it)
347
        if (observer) {
711✔
348
            we_are_alone = observer->no_concurrent_writer_seen();
702✔
349
        }
702✔
350
        if (we_are_alone || (retry_count > 0 && elapsed > max_retry_period)) {
711✔
351
            auto str = util::format("unable to decrypt after %1 seconds (retry_count=%2, from=%3, size=%4)",
45✔
352
                                    std::chrono::duration_cast<std::chrono::seconds>(elapsed).count(), retry_count,
45✔
353
                                    debug_from, size);
45✔
354
            // std::cerr << std::endl << "*Timeout: " << str << std::endl;
355
            throw DecryptionFailed(str);
45✔
356
        }
45✔
357
        else {
666✔
358
            // don't wait on the first retry as we want to optimize the case where the first read
359
            // from the iv table cache didn't validate and we are fetching the iv block from disk for the first time
360
            auto cur_iv_and_data_hash = std::make_pair(iv, std::hash<std::string_view>{}(page_data));
666✔
361
            if (retry_count != 0) {
666✔
362
                if (last_iv_and_data_hash == cur_iv_and_data_hash) {
369✔
363
                    ++num_identical_reads;
369✔
364
                }
369✔
365
                // don't retry right away if there are potentially other external writers
366
                std::this_thread::yield();
369✔
367
            }
369✔
368
            last_iv_and_data_hash = cur_iv_and_data_hash;
666✔
369
            ++retry_count;
666✔
370
        }
666✔
371
    };
711✔
372

373
    auto should_retry = [&]() -> bool {
363,642✔
374
        // if we don't have an observer object, we're guaranteed to be alone in the world,
375
        // and retrying will not help us, since the file is not being changed.
376
        if (!observer)
64,386✔
377
            return false;
63,345✔
378
        // if no-one is mutating the file, retrying will also not help:
379
        if (observer && observer->no_concurrent_writer_seen())
1,041✔
380
            return false;
519✔
381
        // if we do not observe identical data or iv within several sequential reads then
382
        // this is a multiprocess reader starvation scenario so keep trying until we get a match
383
        return retry_count <= 5 || (retry_count - num_identical_reads > 1 && retry_count < 20);
522!
384
    };
1,041✔
385

386
    size_t bytes_read = 0;
363,642✔
387
    while (bytes_read < size) {
867,438✔
388
        ssize_t actual = check_read(fd, real_offset(pos), m_rw_buffer.get(), block_size);
570,456✔
389

390
        if (actual == 0)
570,456✔
391
            return bytes_read;
2,772✔
392

393
        iv_table& iv = get_iv_table(fd, pos, retry_count == 0 ? IVLookupMode::UseCache : IVLookupMode::Refetch);
567,684✔
394
        if (iv.iv1 == 0) {
567,684✔
395
            if (should_retry()) {
64,386✔
396
                retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "iv1 == 0");
522✔
397
                continue;
522✔
398
            }
522✔
399
            // This block has never been written to, so we've just read pre-allocated
400
            // space. No memset() since the code using this doesn't rely on
401
            // pre-allocated space being zeroed.
402
            return bytes_read;
63,864✔
403
        }
64,386✔
404

405
        if (!check_hmac(m_rw_buffer.get(), actual, iv.hmac1)) {
503,298✔
406
            // Either the DB is corrupted or we were interrupted between writing the
407
            // new IV and writing the data
408
            if (iv.iv2 == 0) {
219✔
409
                if (should_retry()) {
×
410
                    retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "iv2 == 0");
×
411
                    continue;
×
UNCOV
412
                }
×
413
                // Very first write was interrupted
414
                return bytes_read;
×
UNCOV
415
            }
×
416

417
            if (check_hmac(m_rw_buffer.get(), actual, iv.hmac2)) {
219✔
418
                // Un-bump the IV since the write with the bumped IV never actually
419
                // happened
420
                memcpy(&iv.iv1, &iv.iv2, 32);
6✔
421
            }
6✔
422
            else {
213✔
423
                // If the file has been shrunk and then re-expanded, we may have
424
                // old hmacs that don't go with this data. ftruncate() is
425
                // required to fill any added space with zeroes, so assume that's
426
                // what happened if the buffer is all zeroes
427
                ssize_t i;
213✔
428
                for (i = 0; i < actual; ++i) {
98,517✔
429
                    if (m_rw_buffer[i] != 0) {
98,493✔
430
                        break;
189✔
431
                    }
189✔
432
                }
98,493✔
433
                if (i != actual) {
213✔
434
                    // at least one byte wasn't zero
435
                    retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "i != bytes_read");
189✔
436
                    continue;
189✔
437
                }
189✔
438
                return bytes_read;
24✔
439
            }
213✔
440
        }
219✔
441

442
        // We may expect some adress ranges of the destination buffer of
443
        // AESCryptor::read() to stay unmodified, i.e. being overwritten with
444
        // the same bytes as already present, and may have read-access to these
445
        // from other threads while decryption is taking place.
446
        //
447
        // However, some implementations of AES_cbc_encrypt(), in particular
448
        // OpenSSL, will put garbled bytes as an intermediate step during the
449
        // operation which will lead to incorrect data being read by other
450
        // readers concurrently accessing that page. Incorrect data leads to
451
        // crashes.
452
        //
453
        // We therefore decrypt to a temporary buffer first and then copy the
454
        // completely decrypted data after.
455
        crypt(mode_Decrypt, pos, m_dst_buffer.get(), m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
503,085✔
456
        memcpy(dst, m_dst_buffer.get(), block_size);
503,085✔
457

458
        pos += block_size;
503,085✔
459
        dst += block_size;
503,085✔
460
        bytes_read += block_size;
503,085✔
461
        retry_count = 0;
503,085✔
462
    }
503,085✔
463
    return bytes_read;
296,982✔
464
}
363,642✔
465

466
void AESCryptor::try_read_block(FileDesc fd, off_t pos, char* dst) noexcept
467
{
×
UNCOV
468
    ssize_t bytes_read = check_read(fd, real_offset(pos), m_rw_buffer.get(), block_size);
×
469

470
    if (bytes_read == 0) {
×
471
        std::cerr << "Read failed: 0x" << std::hex << pos << std::endl;
×
472
        memset(dst, 0x55, block_size);
×
473
        return;
×
UNCOV
474
    }
×
475

476
    iv_table& iv = get_iv_table(fd, pos, IVLookupMode::Refetch);
×
477
    if (iv.iv1 == 0) {
×
478
        std::cerr << "Block never written: 0x" << std::hex << pos << std::endl;
×
479
        memset(dst, 0xAA, block_size);
×
480
        return;
×
UNCOV
481
    }
×
482

483
    if (!check_hmac(m_rw_buffer.get(), bytes_read, iv.hmac1)) {
×
484
        if (iv.iv2 == 0) {
×
485
            std::cerr << "First write interrupted: 0x" << std::hex << pos << std::endl;
×
UNCOV
486
        }
×
487

488
        if (check_hmac(m_rw_buffer.get(), bytes_read, iv.hmac2)) {
×
489
            std::cerr << "Restore old IV: 0x" << std::hex << pos << std::endl;
×
490
            memcpy(&iv.iv1, &iv.iv2, 32);
×
491
        }
×
492
        else {
×
493
            std::cerr << "Checksum failed: 0x" << std::hex << pos << std::endl;
×
494
        }
×
495
    }
×
496
    crypt(mode_Decrypt, pos, dst, m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
×
UNCOV
497
}
×
498

499
void AESCryptor::write(FileDesc fd, off_t pos, const char* src, size_t size, WriteMarker* marker) noexcept
500
{
72,765✔
501
    REALM_ASSERT(size % block_size == 0);
72,765✔
502
    while (size > 0) {
192,147✔
503
        iv_table& iv = get_iv_table(fd, pos);
119,382✔
504

505
        memcpy(&iv.iv2, &iv.iv1, 32); // this is also copying the hmac
119,382✔
506
        do {
119,382✔
507
            ++iv.iv1;
119,382✔
508
            // 0 is reserved for never-been-used, so bump if we just wrapped around
509
            if (iv.iv1 == 0)
119,382✔
UNCOV
510
                ++iv.iv1;
×
511

512
            crypt(mode_Encrypt, pos, m_rw_buffer.get(), src, reinterpret_cast<const char*>(&iv.iv1));
119,382✔
513
            hmac_sha224(Span(reinterpret_cast<uint8_t*>(m_rw_buffer.get()), block_size), iv.hmac1, m_hmacKey);
119,382✔
514
            // In the extremely unlikely case that both the old and new versions have
515
            // the same hash we won't know which IV to use, so bump the IV until
516
            // they're different.
517
        } while (REALM_UNLIKELY(iv.hmac1 == iv.hmac2));
119,382✔
518

519
        if (marker)
119,382✔
520
            marker->mark(pos);
11,124✔
521
        check_write(fd, iv_table_pos(pos), &iv, sizeof(iv));
119,382✔
522
        check_write(fd, real_offset(pos), m_rw_buffer.get(), block_size);
119,382✔
523
        if (marker)
119,382✔
524
            marker->unmark();
11,124✔
525

526
        pos += block_size;
119,382✔
527
        src += block_size;
119,382✔
528
        size -= block_size;
119,382✔
529
    }
119,382✔
530
}
72,765✔
531

532
void AESCryptor::crypt(EncryptionMode mode, off_t pos, char* dst, const char* src, const char* stored_iv) noexcept
533
{
622,467✔
534
    uint8_t iv[aes_block_size] = {0};
622,467✔
535
    memcpy(iv, stored_iv, 4);
622,467✔
536
    memcpy(iv + 4, &pos, sizeof(pos));
622,467✔
537

538
#if REALM_PLATFORM_APPLE
328,200✔
539
    CCCryptorRef cryptor = mode == mode_Encrypt ? m_encr : m_decr;
328,200✔
540
    CCCryptorReset(cryptor, iv);
328,200✔
541

542
    size_t bytesEncrypted = 0;
328,200✔
543
    CCCryptorStatus err = CCCryptorUpdate(cryptor, src, block_size, dst, block_size, &bytesEncrypted);
328,200✔
544
    REALM_ASSERT(err == kCCSuccess);
328,200✔
545
    REALM_ASSERT(bytesEncrypted == block_size);
328,200✔
546
#elif defined(_WIN32)
547
    ULONG cbData;
548
    int i;
549

550
    if (mode == mode_Encrypt) {
551
        i = BCryptEncrypt(m_aes_key_handle, (PUCHAR)src, block_size, nullptr, (PUCHAR)iv, sizeof(iv), (PUCHAR)dst,
552
                          block_size, &cbData, 0);
553
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptEncrypt()", i);
554
        REALM_ASSERT_RELEASE_EX(cbData == block_size && "BCryptEncrypt()", cbData);
555
    }
556
    else if (mode == mode_Decrypt) {
557
        i = BCryptDecrypt(m_aes_key_handle, (PUCHAR)src, block_size, nullptr, (PUCHAR)iv, sizeof(iv), (PUCHAR)dst,
558
                          block_size, &cbData, 0);
559
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptDecrypt()", i);
560
        REALM_ASSERT_RELEASE_EX(cbData == block_size && "BCryptDecrypt()", cbData);
561
    }
562
    else {
563
        REALM_UNREACHABLE();
564
    }
565

566
#else
567
    if (!EVP_CipherInit_ex(m_ctx, EVP_aes_256_cbc(), NULL, m_aesKey.data(), iv, mode))
294,267✔
568
        handle_error();
569

570
    int len;
294,267✔
571
    // Use zero padding - we always write a whole page
572
    EVP_CIPHER_CTX_set_padding(m_ctx, 0);
294,267✔
573

574
    if (!EVP_CipherUpdate(m_ctx, reinterpret_cast<uint8_t*>(dst), &len, reinterpret_cast<const uint8_t*>(src),
294,267✔
575
                          block_size))
294,267✔
576
        handle_error();
577

578
    // Finalize the encryption. Should not output further data.
579
    if (!EVP_CipherFinal_ex(m_ctx, reinterpret_cast<uint8_t*>(dst) + len, &len))
294,267✔
580
        handle_error();
581
#endif
294,267✔
582
}
622,467✔
583

584
EncryptedFileMapping::EncryptedFileMapping(SharedFileInfo& file, size_t file_offset, void* addr, size_t size,
585
                                           File::AccessMode access, util::WriteObserver* observer,
586
                                           util::WriteMarker* marker)
587
    : m_file(file)
7,677✔
588
    , m_page_shift(log2(realm::util::page_size()))
7,677✔
589
    , m_blocks_per_page(static_cast<size_t>(1ULL << m_page_shift) / block_size)
7,677✔
590
    , m_num_decrypted(0)
7,677✔
591
    , m_access(access)
7,677✔
592
    , m_observer(observer)
7,677✔
593
    , m_marker(marker)
7,677✔
594
#ifdef REALM_DEBUG
595
    , m_validate_buffer(new char[static_cast<size_t>(1ULL << m_page_shift)])
7,677✔
596
#endif
597
{
14,808✔
598
    REALM_ASSERT(m_blocks_per_page * block_size == static_cast<size_t>(1ULL << m_page_shift));
14,808✔
599
    set(addr, size, file_offset); // throws
14,808✔
600
    file.mappings.push_back(this);
14,808✔
601
}
14,808✔
602

603
EncryptedFileMapping::~EncryptedFileMapping()
604
{
14,808✔
605
    for (auto& e : m_page_state) {
2,505,498✔
606
        REALM_ASSERT(is_not(e, Writable));
2,505,498✔
607
    }
2,505,498✔
608
    if (m_access == File::access_ReadWrite) {
14,808✔
609
        flush();
7,329✔
610
        sync();
7,329✔
611
    }
7,329✔
612
    m_file.mappings.erase(remove(m_file.mappings.begin(), m_file.mappings.end(), this));
14,808✔
613
}
14,808✔
614

615
char* EncryptedFileMapping::page_addr(size_t local_page_ndx) const noexcept
616
{
575,184✔
617
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
575,184✔
618
    return static_cast<char*>(m_addr) + (local_page_ndx << m_page_shift);
575,184✔
619
}
575,184✔
620

621
void EncryptedFileMapping::mark_outdated(size_t local_page_ndx) noexcept
622
{
2,376✔
623
    if (local_page_ndx >= m_page_state.size())
2,376✔
UNCOV
624
        return;
×
625
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], UpToDate));
2,376✔
626
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Dirty));
2,376✔
627
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Writable));
2,376✔
628

629
    size_t chunk_ndx = local_page_ndx >> page_to_chunk_shift;
2,376✔
630
    if (m_chunk_dont_scan[chunk_ndx])
2,376✔
UNCOV
631
        m_chunk_dont_scan[chunk_ndx] = 0;
×
632
}
2,376✔
633

634
bool EncryptedFileMapping::copy_up_to_date_page(size_t local_page_ndx) noexcept
635
{
148,476✔
636
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
148,476✔
637
    // Precondition: this method must never be called for a page which
638
    // is already up to date.
639
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], UpToDate));
148,476✔
640
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
420,651✔
641
        EncryptedFileMapping* m = m_file.mappings[i];
276,606✔
642
        size_t page_ndx_in_file = local_page_ndx + m_first_page;
276,606✔
643
        if (m == this || !m->contains_page(page_ndx_in_file))
276,606✔
644
            continue;
269,208✔
645

646
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
7,398✔
647
        if (is(m->m_page_state[shadow_mapping_local_ndx], UpToDate)) {
7,398✔
648
            memcpy(page_addr(local_page_ndx), m->page_addr(shadow_mapping_local_ndx),
4,431✔
649
                   static_cast<size_t>(1ULL << m_page_shift));
4,431✔
650
            return true;
4,431✔
651
        }
4,431✔
652
    }
7,398✔
653
    return false;
144,045✔
654
}
148,476✔
655

656
void EncryptedFileMapping::refresh_page(size_t local_page_ndx, size_t required)
657
{
148,476✔
658
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
148,476✔
659
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Dirty));
148,476✔
660
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Writable));
148,476✔
661
    char* addr = page_addr(local_page_ndx);
148,476✔
662

663
    if (!copy_up_to_date_page(local_page_ndx)) {
148,476✔
664
        const size_t page_ndx_in_file = local_page_ndx + m_first_page;
144,045✔
665
        const size_t end_page_ndx_in_file = m_first_page + m_page_state.size();
144,045✔
666
        off_t data_pos = off_t(page_ndx_in_file << m_page_shift);
144,045✔
667
        if (is(m_page_state[local_page_ndx], StaleIV)) {
144,045✔
668
            auto refreshed_ivs =
4,620✔
669
                m_file.cryptor.refresh_ivs(m_file.fd, data_pos, page_ndx_in_file, end_page_ndx_in_file);
4,620✔
670
            for (const auto& [page_ndx, state] : refreshed_ivs) {
16,998✔
671
                size_t local_page_ndx_of_iv_change = page_ndx - m_first_page;
16,998✔
672
                REALM_ASSERT_EX(contains_page(page_ndx), page_ndx, m_first_page, m_page_state.size());
16,998✔
673
                if (is(m_page_state[local_page_ndx_of_iv_change], Dirty | Writable)) {
16,998✔
674
                    continue;
×
UNCOV
675
                }
×
676
                switch (state) {
16,998✔
677
                    case IVRefreshState::UpToDate:
3,516✔
678
                        if (is(m_page_state[local_page_ndx_of_iv_change], StaleIV)) {
3,516✔
679
                            set(m_page_state[local_page_ndx_of_iv_change], UpToDate);
1,299✔
680
                            clear(m_page_state[local_page_ndx_of_iv_change], StaleIV);
1,299✔
681
                        }
1,299✔
682
                        break;
3,516✔
683
                    case IVRefreshState::RequiresRefresh:
13,482✔
684
                        clear(m_page_state[local_page_ndx_of_iv_change], StaleIV);
13,482✔
685
                        clear(m_page_state[local_page_ndx_of_iv_change], UpToDate);
13,482✔
686
                        break;
13,482✔
687
                }
16,998✔
688
            }
16,998✔
689
            REALM_ASSERT_EX(refreshed_ivs.count(page_ndx_in_file) == 1, page_ndx_in_file, refreshed_ivs.size());
4,620✔
690
            if (refreshed_ivs[page_ndx_in_file] == IVRefreshState::UpToDate) {
4,620✔
691
                return;
1,092✔
692
            }
1,092✔
693
        }
4,620✔
694
        size_t size = static_cast<size_t>(1ULL << m_page_shift);
142,953✔
695
        size_t actual = m_file.cryptor.read(m_file.fd, data_pos, addr, size, m_observer);
142,953✔
696
        if (actual < size) {
142,953✔
697
            if (actual >= required) {
66,660✔
698
                memset(addr + actual, 0x55, size - actual);
66,642✔
699
            }
66,642✔
700
            else {
18✔
701
                size_t fs = to_size_t(File::get_size_static(m_file.fd));
18✔
702
                throw DecryptionFailed(
18✔
703
                    util::format("failed to decrypt block %1 in file of size %2", local_page_ndx + m_first_page, fs));
18✔
704
            }
18✔
705
        }
66,660✔
706
    }
142,953✔
707
    if (is_not(m_page_state[local_page_ndx], UpToDate))
147,366✔
708
        m_num_decrypted++;
147,321✔
709
    set(m_page_state[local_page_ndx], UpToDate);
147,366✔
710
    clear(m_page_state[local_page_ndx], StaleIV);
147,366✔
711
}
147,366✔
712

713
void EncryptedFileMapping::mark_pages_for_IV_check()
714
{
8,238✔
715
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
18,408✔
716
        EncryptedFileMapping* m = m_file.mappings[i];
10,170✔
717
        for (size_t pg = m->get_start_index(); pg < m->get_end_index(); ++pg) {
547,203✔
718
            size_t local_page_ndx = pg - m->m_first_page;
537,033✔
719
            if (is(m->m_page_state[local_page_ndx], UpToDate) &&
537,033✔
720
                is_not(m->m_page_state[local_page_ndx], Dirty | Writable)) {
537,033✔
721
                REALM_ASSERT(is_not(m->m_page_state[local_page_ndx], StaleIV));
7,764✔
722
                clear(m->m_page_state[local_page_ndx], UpToDate);
7,764✔
723
                set(m->m_page_state[local_page_ndx], StaleIV);
7,764✔
724
            }
7,764✔
725
        }
537,033✔
726
    }
10,170✔
727
}
8,238✔
728

729
void EncryptedFileMapping::write_and_update_all(size_t local_page_ndx, size_t begin_offset,
730
                                                size_t end_offset) noexcept
731
{
95,418✔
732
    REALM_ASSERT(is(m_page_state[local_page_ndx], Writable));
95,418✔
733
    REALM_ASSERT(is(m_page_state[local_page_ndx], UpToDate));
95,418✔
734
    // Go through all other mappings of this file and copy changes into those mappings
735
    size_t page_ndx_in_file = local_page_ndx + m_first_page;
95,418✔
736
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
286,947✔
737
        EncryptedFileMapping* m = m_file.mappings[i];
191,529✔
738
        if (m != this && m->contains_page(page_ndx_in_file)) {
191,529✔
739
            size_t shadow_local_page_ndx = page_ndx_in_file - m->m_first_page;
31,767✔
740
            if (is(m->m_page_state[shadow_local_page_ndx], UpToDate) ||
31,767✔
741
                is(m->m_page_state[shadow_local_page_ndx], StaleIV)) { // only keep up to data pages up to date
31,767✔
742
                memcpy(m->page_addr(shadow_local_page_ndx) + begin_offset, page_addr(local_page_ndx) + begin_offset,
29,391✔
743
                       end_offset - begin_offset);
29,391✔
744
                if (is(m->m_page_state[shadow_local_page_ndx], StaleIV)) {
29,391✔
745
                    set(m->m_page_state[shadow_local_page_ndx], UpToDate);
123✔
746
                    clear(m->m_page_state[shadow_local_page_ndx], StaleIV);
123✔
747
                }
123✔
748
            }
29,391✔
749
            else {
2,376✔
750
                m->mark_outdated(shadow_local_page_ndx);
2,376✔
751
            }
2,376✔
752
        }
31,767✔
753
    }
191,529✔
754
    set(m_page_state[local_page_ndx], Dirty);
95,418✔
755
    clear(m_page_state[local_page_ndx], Writable);
95,418✔
756
    clear(m_page_state[local_page_ndx], StaleIV);
95,418✔
757
    size_t chunk_ndx = local_page_ndx >> page_to_chunk_shift;
95,418✔
758
    if (m_chunk_dont_scan[chunk_ndx])
95,418✔
UNCOV
759
        m_chunk_dont_scan[chunk_ndx] = 0;
×
760
}
95,418✔
761

762

763
void EncryptedFileMapping::validate_page(size_t local_page_ndx) noexcept
764
{
10,888,701✔
765
#ifdef REALM_DEBUG
10,888,701✔
766
    REALM_ASSERT(local_page_ndx < m_page_state.size());
10,888,701✔
767
    if (is_not(m_page_state[local_page_ndx], UpToDate))
10,888,701✔
768
        return;
10,668,084✔
769

770
    const size_t page_ndx_in_file = local_page_ndx + m_first_page;
220,617✔
771
    if (!m_file.cryptor.read(m_file.fd, off_t(page_ndx_in_file << m_page_shift), m_validate_buffer.get(),
220,617✔
772
                             static_cast<size_t>(1ULL << m_page_shift), m_observer))
220,617✔
UNCOV
773
        return;
×
774

775
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
668,685✔
776
        EncryptedFileMapping* m = m_file.mappings[i];
448,068✔
777
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
448,068✔
778
        if (m != this && m->contains_page(page_ndx_in_file) && is(m->m_page_state[shadow_mapping_local_ndx], Dirty)) {
448,068✔
779
            memcpy(m_validate_buffer.get(), m->page_addr(shadow_mapping_local_ndx),
×
780
                   static_cast<size_t>(1ULL << m_page_shift));
×
781
            break;
×
UNCOV
782
        }
×
783
    }
448,068✔
784

785
    if (memcmp(m_validate_buffer.get(), page_addr(local_page_ndx), static_cast<size_t>(1ULL << m_page_shift))) {
220,617✔
786
        std::cerr << "mismatch " << this << ": fd(" << m_file.fd << ")"
×
787
                  << "page(" << local_page_ndx << "/" << m_page_state.size() << ") " << m_validate_buffer.get() << " "
×
UNCOV
788
                  << page_addr(local_page_ndx) << std::endl;
×
789
        REALM_TERMINATE("");
UNCOV
790
    }
×
791
#else
792
    static_cast<void>(local_page_ndx);
793
#endif
794
}
220,617✔
795

796
void EncryptedFileMapping::validate() noexcept
797
{
33,417✔
798
#ifdef REALM_DEBUG
33,417✔
799
    const size_t num_local_pages = m_page_state.size();
33,417✔
800
    for (size_t local_page_ndx = 0; local_page_ndx < num_local_pages; ++local_page_ndx)
5,513,724✔
801
        validate_page(local_page_ndx);
5,480,307✔
802
#endif
33,417✔
803
}
33,417✔
804

805
void EncryptedFileMapping::reclaim_page(size_t page_ndx)
UNCOV
806
{
×
807
#ifdef _WIN32
808
    // On windows we don't know how to replace a page within a page range with a fresh one.
809
    // instead we clear it. If the system runs with same-page-merging, this will reduce
810
    // the number of used pages.
811
    memset(page_addr(page_ndx), 0, static_cast<size_t>(1) << m_page_shift);
812
#else
813
    // On Posix compatible, we can request a new page in the middle of an already
814
    // requested range, so that's what we do. This releases the backing store for the
815
    // old page and gives us a shared zero-page that we can later demand-allocate, thus
816
    // reducing the overall amount of used physical pages.
817
    void* addr = page_addr(page_ndx);
×
818
    void* addr2 = ::mmap(addr, 1 << m_page_shift, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
×
819
    if (addr != addr2) {
×
820
        if (addr2 == 0) {
×
821
            int err = errno;
×
822
            throw SystemError(err, get_errno_msg("using mmap() to clear page failed", err));
×
823
        }
×
824
        throw std::runtime_error("internal error in mmap()");
×
825
    }
×
826
#endif
×
UNCOV
827
}
×
828

829
/* This functions is a bit convoluted. It reclaims pages, but only does a limited amount of work
830
 * each time it's called. It saves the progress in a 'progress_ptr' so that it can resume later
831
 * from where it was stopped.
832
 *
833
 * The workload is composed of workunits, each unit signifying
834
 * 1) A scanning of the state of 4K pages
835
 * 2) One system call (to mmap to release a page and get a new one)
836
 * 3) A scanning of 1K entries in the "don't scan" array (corresponding to 4M pages)
837
 * Approximately
838
 */
839
void EncryptedFileMapping::reclaim_untouched(size_t& progress_index, size_t& work_limit) noexcept
840
{
×
841
    const auto scan_amount_per_workunit = 4096;
×
842
    bool contiguous_scan = false;
×
843
    size_t next_scan_payment = scan_amount_per_workunit;
×
UNCOV
844
    const size_t last_index = get_end_index();
×
845

846
    auto done_some_work = [&]() {
×
847
        if (work_limit > 0)
×
848
            work_limit--;
×
UNCOV
849
    };
×
850

851
    auto visit_and_potentially_reclaim = [&](size_t page_ndx) {
×
852
        PageState& ps = m_page_state[page_ndx];
×
853
        if (is(ps, UpToDate)) {
×
854
            if (is_not(ps, Touched) && is_not(ps, Dirty) && is_not(ps, Writable)) {
×
855
                clear(ps, UpToDate);
×
856
                reclaim_page(page_ndx);
×
857
                m_num_decrypted--;
×
858
                done_some_work();
×
859
            }
×
860
            contiguous_scan = false;
×
861
        }
×
862
        clear(ps, Touched);
×
UNCOV
863
    };
×
864

865
    auto skip_chunk_if_possible = [&](size_t& page_ndx) // update vars corresponding to skipping a chunk if possible
×
866
    {
×
867
        size_t chunk_ndx = page_ndx >> page_to_chunk_shift;
×
UNCOV
868
        if (m_chunk_dont_scan[chunk_ndx]) {
×
869
            // skip to end of chunk
870
            page_ndx = ((chunk_ndx + 1) << page_to_chunk_shift) - 1;
×
UNCOV
871
            progress_index = m_first_page + page_ndx;
×
872
            // postpone next scan payment
873
            next_scan_payment += page_to_chunk_factor;
×
874
            return true;
×
875
        }
×
876
        else
×
877
            return false;
×
UNCOV
878
    };
×
879

880
    auto is_last_page_in_chunk = [](size_t page_ndx) {
×
881
        auto page_to_chunk_mask = page_to_chunk_factor - 1;
×
882
        return (page_ndx & page_to_chunk_mask) == page_to_chunk_mask;
×
883
    };
×
884
    auto is_first_page_in_chunk = [](size_t page_ndx) {
×
885
        auto page_to_chunk_mask = page_to_chunk_factor - 1;
×
886
        return (page_ndx & page_to_chunk_mask) == 0;
×
UNCOV
887
    };
×
888

889
    while (work_limit > 0 && progress_index < last_index) {
×
890
        size_t page_ndx = progress_index - m_first_page;
×
891
        if (!skip_chunk_if_possible(page_ndx)) {
×
892
            if (is_first_page_in_chunk(page_ndx)) {
×
893
                contiguous_scan = true;
×
894
            }
×
UNCOV
895
            visit_and_potentially_reclaim(page_ndx);
×
896
            // if we've scanned a full chunk contiguously, mark it as not needing scans
897
            if (is_last_page_in_chunk(page_ndx)) {
×
898
                if (contiguous_scan) {
×
899
                    m_chunk_dont_scan[page_ndx >> page_to_chunk_shift] = 1;
×
900
                }
×
901
                contiguous_scan = false;
×
902
            }
×
UNCOV
903
        }
×
904
        // account for work performed:
905
        if (page_ndx >= next_scan_payment) {
×
906
            next_scan_payment = page_ndx + scan_amount_per_workunit;
×
907
            done_some_work();
×
908
        }
×
909
        ++progress_index;
×
910
    }
×
911
    return;
×
UNCOV
912
}
×
913

914
void EncryptedFileMapping::flush() noexcept
915
{
33,417✔
916
    const size_t num_dirty_pages = m_page_state.size();
33,417✔
917
    for (size_t local_page_ndx = 0; local_page_ndx < num_dirty_pages; ++local_page_ndx) {
5,513,724✔
918
        if (is_not(m_page_state[local_page_ndx], Dirty)) {
5,480,307✔
919
            validate_page(local_page_ndx);
5,408,394✔
920
            continue;
5,408,394✔
921
        }
5,408,394✔
922

923
        size_t page_ndx_in_file = local_page_ndx + m_first_page;
71,913✔
924
        m_file.cryptor.write(m_file.fd, off_t(page_ndx_in_file << m_page_shift), page_addr(local_page_ndx),
71,913✔
925
                             static_cast<size_t>(1ULL << m_page_shift), m_marker);
71,913✔
926
        clear(m_page_state[local_page_ndx], Dirty);
71,913✔
927
    }
71,913✔
928

929
    validate();
33,417✔
930
}
33,417✔
931

932
#ifdef _MSC_VER
933
#pragma warning(disable : 4297) // throw in noexcept
934
#endif
935
void EncryptedFileMapping::sync() noexcept
936
{
13,848✔
937
#ifdef _WIN32
938
    if (FlushFileBuffers(m_file.fd))
939
        return;
940
    throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
941
#else
942
    fsync(m_file.fd);
13,848✔
943
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
944
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
945
    //
946
    // See description of fsync on iOS here:
947
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
948
    //
949
    // See also
950
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
951
    // for a discussion of this related to core data.
952
#endif
13,848✔
953
}
13,848✔
954
#ifdef _MSC_VER
955
#pragma warning(default : 4297)
956
#endif
957

958
void EncryptedFileMapping::write_barrier(const void* addr, size_t size) noexcept
959
{
33,267✔
960
    // Propagate changes to all other decrypted pages mapping the same memory
961

962
    REALM_ASSERT(m_access == File::access_ReadWrite);
33,267✔
963
    size_t first_accessed_local_page = get_local_index_of_address(addr);
33,267✔
964
    size_t first_offset = static_cast<const char*>(addr) - page_addr(first_accessed_local_page);
33,267✔
965
    const char* last_accessed_address = static_cast<const char*>(addr) + (size == 0 ? 0 : size - 1);
33,267✔
966
    size_t last_accessed_local_page = get_local_index_of_address(last_accessed_address);
33,267✔
967
    size_t pages_size = m_page_state.size();
33,267✔
968

969
    // propagate changes to first page (update may be partial, may also be to last page)
970
    if (first_accessed_local_page < pages_size) {
33,267✔
971
        REALM_ASSERT_EX(is(m_page_state[first_accessed_local_page], UpToDate),
33,267✔
972
                        m_page_state[first_accessed_local_page]);
33,267✔
973
        if (first_accessed_local_page == last_accessed_local_page) {
33,267✔
974
            size_t last_offset = last_accessed_address - page_addr(first_accessed_local_page);
32,340✔
975
            write_and_update_all(first_accessed_local_page, first_offset, last_offset + 1);
32,340✔
976
        }
32,340✔
977
        else
927✔
978
            write_and_update_all(first_accessed_local_page, first_offset, static_cast<size_t>(1) << m_page_shift);
927✔
979
    }
33,267✔
980
    // propagate changes to pages between first and last page (update only full pages)
981
    for (size_t idx = first_accessed_local_page + 1; idx < last_accessed_local_page && idx < pages_size; ++idx) {
94,491✔
982
        REALM_ASSERT(is(m_page_state[idx], UpToDate));
61,224✔
983
        write_and_update_all(idx, 0, static_cast<size_t>(1) << m_page_shift);
61,224✔
984
    }
61,224✔
985
    // propagate changes to the last page (update may be partial)
986
    if (first_accessed_local_page < last_accessed_local_page && last_accessed_local_page < pages_size) {
33,267✔
987
        REALM_ASSERT(is(m_page_state[last_accessed_local_page], UpToDate));
927✔
988
        size_t last_offset = last_accessed_address - page_addr(last_accessed_local_page);
927✔
989
        write_and_update_all(last_accessed_local_page, 0, last_offset + 1);
927✔
990
    }
927✔
991
}
33,267✔
992

993
void EncryptedFileMapping::read_barrier(const void* addr, size_t size, Header_to_size header_to_size, bool to_modify)
994
{
1,372,467✔
995
    size_t first_accessed_local_page = get_local_index_of_address(addr);
1,372,467✔
996
    size_t page_size = 1ULL << m_page_shift;
1,372,467✔
997
    size_t required = get_offset_of_address(addr) + size;
1,372,467✔
998
    {
1,372,467✔
999
        // make sure the first page is available
1000
        PageState& ps = m_page_state[first_accessed_local_page];
1,372,467✔
1001
        if (is_not(ps, Touched))
1,372,467✔
1002
            set(ps, Touched);
17,781✔
1003
        if (is_not(ps, UpToDate))
1,372,467✔
1004
            refresh_page(first_accessed_local_page, to_modify ? 0 : required);
24,243✔
1005
        if (to_modify)
1,372,467✔
1006
            set(ps, Writable);
33,267✔
1007
    }
1,372,467✔
1008

1009
    // force the page reclaimer to look into pages in this chunk:
1010
    size_t chunk_ndx = first_accessed_local_page >> page_to_chunk_shift;
1,372,467✔
1011
    if (m_chunk_dont_scan[chunk_ndx])
1,372,467✔
UNCOV
1012
        m_chunk_dont_scan[chunk_ndx] = 0;
×
1013

1014
    if (header_to_size) {
1,372,467✔
1015
        // We know it's an array, and array headers are 8-byte aligned, so it is
1016
        // included in the first page which was handled above.
1017
        size = header_to_size(static_cast<const char*>(addr));
1,317,597✔
1018
        required = get_offset_of_address(addr) + size;
1,317,597✔
1019
    }
1,317,597✔
1020

1021
    size_t last_idx = get_local_index_of_address(addr, size == 0 ? 0 : size - 1);
1,372,467✔
1022
    size_t pages_size = m_page_state.size();
1,372,467✔
1023

1024
    // We already checked first_accessed_local_page above, so we start the loop
1025
    // at first_accessed_local_page + 1 to check the following page.
1026
    for (size_t idx = first_accessed_local_page + 1; idx <= last_idx && idx < pages_size; ++idx) {
1,639,305✔
1027
        required -= page_size;
266,838✔
1028
        // force the page reclaimer to look into pages in this chunk
1029
        chunk_ndx = idx >> page_to_chunk_shift;
266,838✔
1030
        if (m_chunk_dont_scan[chunk_ndx])
266,838✔
UNCOV
1031
            m_chunk_dont_scan[chunk_ndx] = 0;
×
1032

1033
        PageState& ps = m_page_state[idx];
266,838✔
1034
        if (is_not(ps, Touched))
266,838✔
1035
            set(ps, Touched);
124,224✔
1036
        if (is_not(ps, UpToDate))
266,838✔
1037
            refresh_page(idx, to_modify ? 0 : required);
124,233✔
1038
        if (to_modify)
266,838✔
1039
            set(ps, Writable);
62,151✔
1040
    }
266,838✔
1041
}
1,372,467✔
1042

1043
void EncryptedFileMapping::extend_to(size_t offset, size_t new_size)
1044
{
4,005✔
1045
    REALM_ASSERT_EX(new_size % page_size() == 0, new_size, page_size());
4,005✔
1046
    size_t num_pages = new_size >> m_page_shift;
4,005✔
1047
    m_page_state.resize(num_pages, PageState::Clean);
4,005✔
1048
    m_chunk_dont_scan.resize((num_pages + page_to_chunk_factor - 1) >> page_to_chunk_shift, false);
4,005✔
1049
    m_file.cryptor.set_file_size((off_t)(offset + new_size));
4,005✔
1050
}
4,005✔
1051

1052
void EncryptedFileMapping::set(void* new_addr, size_t new_size, size_t new_file_offset)
1053
{
14,808✔
1054
    REALM_ASSERT(new_file_offset % (1ULL << m_page_shift) == 0);
14,808✔
1055
    REALM_ASSERT(new_size % (1ULL << m_page_shift) == 0);
14,808✔
1056

1057
    // This seems dangerous - correct operation in a setting with multiple (partial)
1058
    // mappings of the same file would rely on ordering of individual mapping requests.
1059
    // Currently we only ever extend the file - but when we implement continuous defrag,
1060
    // this design should be revisited.
1061
    m_file.cryptor.set_file_size(off_t(new_size + new_file_offset));
14,808✔
1062

1063
    flush();
14,808✔
1064
    m_addr = new_addr;
14,808✔
1065

1066
    m_first_page = new_file_offset >> m_page_shift;
14,808✔
1067
    size_t num_pages = new_size >> m_page_shift;
14,808✔
1068

1069
    m_num_decrypted = 0;
14,808✔
1070
    m_page_state.clear();
14,808✔
1071
    m_chunk_dont_scan.clear();
14,808✔
1072

1073
    m_page_state.resize(num_pages, PageState(0));
14,808✔
1074
    m_chunk_dont_scan.resize((num_pages + page_to_chunk_factor - 1) >> page_to_chunk_shift, false);
14,808✔
1075
}
14,808✔
1076

1077
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1078
{
16,545✔
1079
    if (size == 0)
16,545✔
1080
        return 0;
210✔
1081
    return fake_offset(size);
16,335✔
1082
}
16,545✔
1083

1084
File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept
1085
{
2,514✔
1086
    size_t ps = page_size();
2,514✔
1087
    return real_offset((size + ps - 1) & ~(ps - 1));
2,514✔
1088
}
2,514✔
1089
} // namespace realm::util
1090
#else
1091

1092
namespace realm::util {
1093
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1094
{
1095
    return size;
1096
}
1097

1098
File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept
1099
{
1100
    return size;
1101
}
1102
} // namespace realm::util
1103
#endif // REALM_ENABLE_ENCRYPTION
1104

1105
namespace realm::util {
1106
std::string DecryptionFailed::get_message_with_bt(std::string_view msg)
1107
{
78✔
1108
    auto bt = Backtrace::capture();
78✔
1109
    std::stringstream ss;
78✔
1110
    bt.print(ss);
78✔
1111
    return util::format("Decryption failed: %1\n%2\n", msg, ss.str());
78✔
1112
}
78✔
1113
} // 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