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

realm / realm-core / 1852

20 Nov 2023 07:46PM UTC coverage: 91.656% (-0.04%) from 91.699%
1852

push

Evergreen

web-flow
Fix client reset cycle detection for PBS recovery errors (#7149)

Tracking that a client reset was in progress was done in the same write
transaction as the recovery operation, so if recovery failed the tracking was
rolled back too. This worked for FLX due to that codepath committing before
beginning recovery.

92266 of 169120 branches covered (0.0%)

31 of 31 new or added lines in 3 files covered. (100.0%)

155 existing lines in 15 files now uncovered.

231226 of 252275 relevant lines covered (91.66%)

6144671.4 hits per line

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

83.23
/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
#if REALM_ENABLE_ENCRYPTION
24
#include <realm/util/aes_cryptor.hpp>
25
#include <realm/util/errno.hpp>
26
#include <realm/utilities.hpp>
27
#include <realm/util/sha_crypto.hpp>
28
#include <realm/util/terminate.hpp>
29

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

38
#ifdef REALM_DEBUG
39
#include <cstdio>
40
#endif
41

42
#include <array>
43
#include <cstring>
44
#include <iostream>
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
#include <pthread.h>
54
#endif
55

56
namespace realm::util {
57
SharedFileInfo::SharedFileInfo(const uint8_t* key)
58
    : cryptor(key)
59
{
2,031✔
60
}
2,031✔
61

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

84
struct iv_table {
85
    uint32_t iv1 = 0;
86
    std::array<uint8_t, 28> hmac1 = {};
87
    uint32_t iv2 = 0;
88
    std::array<uint8_t, 28> hmac2 = {};
89
    bool operator==(const iv_table& other) const
90
    {
20,457✔
91
        return iv1 == other.iv1 && iv2 == other.iv2 && hmac1 == other.hmac1 && hmac2 == other.hmac2;
20,457✔
92
    }
20,457✔
93
    bool operator!=(const iv_table& other) const
94
    {
12,669✔
95
        return !(*this == other);
12,669✔
96
    }
12,669✔
97
};
98

99
namespace {
100
const int aes_block_size = 16;
101
const size_t block_size = 4096;
102

103
const size_t metadata_size = sizeof(iv_table);
104
const size_t blocks_per_metadata_block = block_size / metadata_size;
105

106
// map an offset in the data to the actual location in the file
107
template <typename Int>
108
Int real_offset(Int pos)
109
{
606,996✔
110
    REALM_ASSERT(pos >= 0);
606,996✔
111
    const size_t index = static_cast<size_t>(pos) / block_size;
606,996✔
112
    const size_t metadata_page_count = index / blocks_per_metadata_block + 1;
606,996✔
113
    return Int(pos + metadata_page_count * block_size);
606,996✔
114
}
606,996✔
115

116
// map a location in the file to the offset in the data
117
template <typename Int>
118
Int fake_offset(Int pos)
119
{
4,152✔
120
    REALM_ASSERT(pos >= 0);
4,152✔
121
    const size_t index = static_cast<size_t>(pos) / block_size;
4,152✔
122
    const size_t metadata_page_count = (index + blocks_per_metadata_block) / (blocks_per_metadata_block + 1);
4,152✔
123
    return pos - metadata_page_count * block_size;
4,152✔
124
}
4,152✔
125

126
// get the location of the iv_table for the given data (not file) position
127
off_t iv_table_pos(off_t pos)
128
{
115,461✔
129
    REALM_ASSERT(pos >= 0);
115,461✔
130
    const size_t index = static_cast<size_t>(pos) / block_size;
115,461✔
131
    const size_t metadata_block = index / blocks_per_metadata_block;
115,461✔
132
    const size_t metadata_index = index & (blocks_per_metadata_block - 1);
115,461✔
133
    return off_t(metadata_block * (blocks_per_metadata_block + 1) * block_size + metadata_index * metadata_size);
115,461✔
134
}
115,461✔
135

136
void check_write(FileDesc fd, off_t pos, const void* data, size_t len)
137
{
217,332✔
138
    uint64_t orig = File::get_file_pos(fd);
217,332✔
139
    File::seek_static(fd, pos);
217,332✔
140
    File::write_static(fd, static_cast<const char*>(data), len);
217,332✔
141
    File::seek_static(fd, orig);
217,332✔
142
}
217,332✔
143

144
size_t check_read(FileDesc fd, off_t pos, void* dst, size_t len)
145
{
504,612✔
146
    uint64_t orig = File::get_file_pos(fd);
504,612✔
147
    File::seek_static(fd, pos);
504,612✔
148
    size_t ret = File::read_static(fd, static_cast<char*>(dst), len);
504,612✔
149
    File::seek_static(fd, orig);
504,612✔
150
    return ret;
504,612✔
151
}
504,612✔
152

153
} // anonymous namespace
154

155
AESCryptor::AESCryptor(const uint8_t* key)
156
    : m_rw_buffer(new char[block_size])
157
    , m_dst_buffer(new char[block_size])
158
{
2,127✔
159
    memcpy(m_aesKey.data(), key, 32);
2,127✔
160
    memcpy(m_hmacKey.data(), key + 32, 32);
2,127✔
161

900✔
162
#if REALM_PLATFORM_APPLE
1,227✔
163
    // A random iv is passed to CCCryptorReset. This iv is *not used* by Realm; we set it manually prior to
164
    // each call to BCryptEncrypt() and BCryptDecrypt(). We pass this random iv as an attempt to
165
    // suppress a false encryption security warning from the IBM Bluemix Security Analyzer (PR[#2911])
166
    unsigned char u_iv[kCCKeySizeAES256];
1,227✔
167
    arc4random_buf(u_iv, kCCKeySizeAES256);
1,227✔
168
    void* iv = u_iv;
1,227✔
169
    CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_encr);
1,227✔
170
    CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_decr);
1,227✔
171
#elif defined(_WIN32)
172
    BCRYPT_ALG_HANDLE hAesAlg = NULL;
173
    int ret;
174
    ret = BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
175
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptOpenAlgorithmProvider()", ret);
176

177
    ret = BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC,
178
                            sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
179
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptSetProperty()", ret);
180

181
    ret = BCryptGenerateSymmetricKey(hAesAlg, &m_aes_key_handle, nullptr, 0, (PBYTE)key, 32, 0);
182
    REALM_ASSERT_RELEASE_EX(ret == 0 && "BCryptGenerateSymmetricKey()", ret);
183
#else
184
    m_ctx = EVP_CIPHER_CTX_new();
900✔
185
    if (!m_ctx)
900✔
186
        handle_error();
187
#endif
900✔
188
}
2,127✔
189

190
AESCryptor::~AESCryptor() noexcept
191
{
2,127✔
192
#if REALM_PLATFORM_APPLE
1,227✔
193
    CCCryptorRelease(m_encr);
1,227✔
194
    CCCryptorRelease(m_decr);
1,227✔
195
#elif defined(_WIN32)
196
#else
197
    EVP_CIPHER_CTX_cleanup(m_ctx);
900✔
198
    EVP_CIPHER_CTX_free(m_ctx);
900✔
199
#endif
900✔
200
}
2,127✔
201

202
void AESCryptor::check_key(const uint8_t* key)
203
{
1,413✔
204
    if (memcmp(m_aesKey.data(), key, 32) != 0 || memcmp(m_hmacKey.data(), key + 32, 32) != 0)
1,413✔
205
        throw DecryptionFailed();
6✔
206
}
1,413✔
207

208
void AESCryptor::handle_error()
209
{
×
210
    throw std::runtime_error("Error occurred in encryption layer");
×
211
}
×
212

213
void AESCryptor::set_file_size(off_t new_size)
214
{
3,861✔
215
    REALM_ASSERT(new_size >= 0 && !int_cast_has_overflow<size_t>(new_size));
3,861✔
216
    size_t new_size_casted = size_t(new_size);
3,861✔
217
    size_t block_count = (new_size_casted + block_size - 1) / block_size;
3,861✔
218
    m_iv_buffer.reserve((block_count + blocks_per_metadata_block - 1) & ~(blocks_per_metadata_block - 1));
3,861✔
219
    m_iv_buffer_cache.reserve(m_iv_buffer.capacity());
3,861✔
220
}
3,861✔
221

222
iv_table& AESCryptor::get_iv_table(FileDesc fd, off_t data_pos, IVLookupMode mode) noexcept
223
{
606,753✔
224
    REALM_ASSERT(!int_cast_has_overflow<size_t>(data_pos));
606,753✔
225
    size_t data_pos_casted = size_t(data_pos);
606,753✔
226
    size_t idx = data_pos_casted / block_size;
606,753✔
227
    if (mode == IVLookupMode::UseCache && idx < m_iv_buffer.size())
606,753✔
228
        return m_iv_buffer[idx];
600,312✔
229

3,051✔
230
    size_t block_start = std::min(m_iv_buffer.size(), (idx / blocks_per_metadata_block) * blocks_per_metadata_block);
6,441✔
231
    size_t block_end = 1 + idx / blocks_per_metadata_block;
6,441✔
232
    REALM_ASSERT(block_end * blocks_per_metadata_block <= m_iv_buffer.capacity()); // not safe to allocate here
6,441✔
233
    if (block_end * blocks_per_metadata_block > m_iv_buffer.size()) {
6,441✔
234
        m_iv_buffer.resize(block_end * blocks_per_metadata_block);
5,271✔
235
        m_iv_buffer_cache.resize(m_iv_buffer.size());
5,271✔
236
    }
5,271✔
237

3,051✔
238
    for (size_t i = block_start; i < block_end * blocks_per_metadata_block; i += blocks_per_metadata_block) {
13,044✔
239
        off_t iv_pos = iv_table_pos(off_t(i * block_size));
6,795✔
240
        size_t bytes = check_read(fd, iv_pos, &m_iv_buffer[i], block_size);
6,795✔
241
        if (bytes < block_size)
6,795✔
242
            break; // rest is zero-filled by resize()
192✔
243
    }
6,795✔
244

3,051✔
245
    return m_iv_buffer[idx];
6,441✔
246
}
6,441✔
247

248
bool AESCryptor::check_hmac(const void* src, size_t len, const std::array<uint8_t, 28>& hmac) const
249
{
434,427✔
250
    std::array<uint8_t, 224 / 8> buffer;
434,427✔
251
    hmac_sha224(Span(reinterpret_cast<const uint8_t*>(src), len), buffer, m_hmacKey);
434,427✔
252

208,359✔
253
    // Constant-time memcmp to avoid timing attacks
208,359✔
254
    uint8_t result = 0;
434,427✔
255
    for (size_t i = 0; i < 224 / 8; ++i)
12,598,383✔
256
        result |= buffer[i] ^ hmac[i];
12,163,956✔
257
    return result == 0;
434,427✔
258
}
434,427✔
259

260
util::FlatMap<size_t, IVRefreshState>
261
AESCryptor::refresh_ivs(FileDesc fd, off_t data_pos, size_t page_ndx_in_file_expected, size_t end_page_ndx_in_file)
262
{
1,110✔
263
    REALM_ASSERT_EX(page_ndx_in_file_expected < end_page_ndx_in_file, page_ndx_in_file_expected,
1,110✔
264
                    end_page_ndx_in_file);
1,110✔
265
    // the indices returned are page indices, not block indices
576✔
266
    util::FlatMap<size_t, IVRefreshState> page_states;
1,110✔
267

576✔
268
    REALM_ASSERT(!int_cast_has_overflow<size_t>(data_pos));
1,110✔
269
    size_t data_pos_casted = size_t(data_pos);
1,110✔
270
    // the call to get_iv_table() below reads in all ivs in a chunk with size = blocks_per_metadata_block
576✔
271
    // so we will know if any iv in this chunk has changed
576✔
272
    const size_t block_ndx_refresh_start =
1,110✔
273
        ((data_pos_casted / block_size) / blocks_per_metadata_block) * blocks_per_metadata_block;
1,110✔
274
    const size_t block_ndx_refresh_end = block_ndx_refresh_start + blocks_per_metadata_block;
1,110✔
275
    REALM_ASSERT_EX(block_ndx_refresh_end <= m_iv_buffer.size(), block_ndx_refresh_start, block_ndx_refresh_end,
1,110✔
276
                    m_iv_buffer.size());
1,110✔
277

576✔
278
    get_iv_table(fd, data_pos, IVLookupMode::Refetch);
1,110✔
279

576✔
280
    size_t number_of_identical_blocks = 0;
1,110✔
281
    size_t last_page_index = -1;
1,110✔
282
    constexpr iv_table uninitialized_iv = {};
1,110✔
283
    // there may be multiple iv blocks per page so all must be unchanged for a page
576✔
284
    // to be considered unchanged. If any one of the ivs has changed then the entire page
576✔
285
    // must be refreshed. Eg. with a page_size() of 16k and block_size of 4k, if any of
576✔
286
    // the 4 ivs in that page are different, the entire page must be refreshed.
576✔
287
    const size_t num_required_identical_blocks_for_page_match = page_size() / block_size;
1,110✔
288
    for (size_t block_ndx = block_ndx_refresh_start; block_ndx < block_ndx_refresh_end; ++block_ndx) {
13,779✔
289
        size_t page_index = block_ndx * block_size / page_size();
13,626✔
290
        if (page_index >= end_page_ndx_in_file) {
13,626✔
291
            break;
957✔
292
        }
957✔
293
        if (page_index != last_page_index) {
12,669✔
294
            number_of_identical_blocks = 0;
7,521✔
295
        }
7,521✔
296
        if (m_iv_buffer_cache[block_ndx] != m_iv_buffer[block_ndx] || m_iv_buffer[block_ndx] == uninitialized_iv) {
12,669✔
297
            page_states[page_index] = IVRefreshState::RequiresRefresh;
5,064✔
298
            m_iv_buffer_cache[block_ndx] = m_iv_buffer[block_ndx];
5,064✔
299
        }
5,064✔
300
        else {
7,605✔
301
            ++number_of_identical_blocks;
7,605✔
302
        }
7,605✔
303
        if (number_of_identical_blocks >= num_required_identical_blocks_for_page_match) {
12,669✔
304
            REALM_ASSERT_EX(page_states.count(page_index) == 0, page_index, page_ndx_in_file_expected);
4,509✔
305
            page_states[page_index] = IVRefreshState::UpToDate;
4,509✔
306
        }
4,509✔
307
        last_page_index = page_index;
12,669✔
308
    }
12,669✔
309
    REALM_ASSERT_EX(page_states.count(page_ndx_in_file_expected) == 1, page_states.size(), page_ndx_in_file_expected,
1,110✔
310
                    block_ndx_refresh_start, blocks_per_metadata_block);
1,110✔
311
    return page_states;
1,110✔
312
}
1,110✔
313

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

258,771✔
358
    auto should_retry = [&]() -> bool {
271,491✔
359
        // if we don't have an observer object, we're guaranteed to be alone in the world,
49,908✔
360
        // and retrying will not help us, since the file is not being changed.
49,908✔
361
        if (!observer)
62,628✔
362
            return false;
62,610✔
363
        // if no-one is mutating the file, retrying will also not help:
364
        if (observer && observer->no_concurrent_writer_seen())
18✔
365
            return false;
6✔
366
        // if we do not observe identical data or iv within several sequential reads then
367
        // this is a multiprocess reader starvation scenario so keep trying until we get a match
368
        return retry_count <= 5 || (retry_count - num_identical_reads > 1 && retry_count < 20);
12!
369
    };
12✔
370

258,771✔
371
    size_t bytes_read = 0;
328,278✔
372
    while (bytes_read < size) {
762,639✔
373
        ssize_t actual = check_read(fd, real_offset(pos), m_rw_buffer.get(), block_size);
497,817✔
374

258,804✔
375
        if (actual == 0)
497,817✔
376
            return bytes_read;
840✔
377

258,228✔
378
        iv_table& iv = get_iv_table(fd, pos, retry_count == 0 ? IVLookupMode::UseCache : IVLookupMode::Refetch);
496,977✔
379
        if (iv.iv1 == 0) {
496,977✔
380
            if (should_retry()) {
62,628✔
381
                retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "iv1 == 0");
12✔
382
                continue;
12✔
383
            }
12✔
384
            // This block has never been written to, so we've just read pre-allocated
49,908✔
385
            // space. No memset() since the code using this doesn't rely on
49,908✔
386
            // pre-allocated space being zeroed.
49,908✔
387
            return bytes_read;
62,616✔
388
        }
62,616✔
389

208,320✔
390
        if (!check_hmac(m_rw_buffer.get(), actual, iv.hmac1)) {
434,349✔
391
            // Either the DB is corrupted or we were interrupted between writing the
39✔
392
            // new IV and writing the data
39✔
393
            if (iv.iv2 == 0) {
78✔
394
                if (should_retry()) {
×
395
                    retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "iv2 == 0");
×
396
                    continue;
×
397
                }
×
398
                // Very first write was interrupted
399
                return bytes_read;
×
400
            }
×
401

39✔
402
            if (check_hmac(m_rw_buffer.get(), actual, iv.hmac2)) {
78✔
403
                // Un-bump the IV since the write with the bumped IV never actually
3✔
404
                // happened
3✔
405
                memcpy(&iv.iv1, &iv.iv2, 32);
6✔
406
            }
6✔
407
            else {
72✔
408
                // If the file has been shrunk and then re-expanded, we may have
36✔
409
                // old hmacs that don't go with this data. ftruncate() is
36✔
410
                // required to fill any added space with zeroes, so assume that's
36✔
411
                // what happened if the buffer is all zeroes
36✔
412
                ssize_t i;
72✔
413
                for (i = 0; i < actual; ++i) {
72✔
414
                    if (m_rw_buffer[i] != 0) {
72✔
415
                        break;
72✔
416
                    }
72✔
417
                }
72✔
418
                if (i != actual) {
72✔
419
                    // at least one byte wasn't zero
36✔
420
                    retry(std::string_view{m_rw_buffer.get(), block_size}, iv, "i != bytes_read");
72✔
421
                    continue;
72✔
422
                }
72✔
423
                return bytes_read;
×
424
            }
×
425
        }
78✔
426

208,284✔
427
        // We may expect some adress ranges of the destination buffer of
208,284✔
428
        // AESCryptor::read() to stay unmodified, i.e. being overwritten with
208,284✔
429
        // the same bytes as already present, and may have read-access to these
208,284✔
430
        // from other threads while decryption is taking place.
208,284✔
431
        //
208,284✔
432
        // However, some implementations of AES_cbc_encrypt(), in particular
208,284✔
433
        // OpenSSL, will put garbled bytes as an intermediate step during the
208,284✔
434
        // operation which will lead to incorrect data being read by other
208,284✔
435
        // readers concurrently accessing that page. Incorrect data leads to
208,284✔
436
        // crashes.
208,284✔
437
        //
208,284✔
438
        // We therefore decrypt to a temporary buffer first and then copy the
208,284✔
439
        // completely decrypted data after.
208,284✔
440
        crypt(mode_Decrypt, pos, m_dst_buffer.get(), m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
434,277✔
441
        memcpy(dst, m_dst_buffer.get(), block_size);
434,277✔
442

208,284✔
443
        pos += block_size;
434,277✔
444
        dst += block_size;
434,277✔
445
        bytes_read += block_size;
434,277✔
446
        retry_count = 0;
434,277✔
447
    }
434,277✔
448
    return bytes_read;
315,306✔
449
}
328,278✔
450

451
void AESCryptor::try_read_block(FileDesc fd, off_t pos, char* dst) noexcept
452
{
×
453
    ssize_t bytes_read = check_read(fd, real_offset(pos), m_rw_buffer.get(), block_size);
×
454

455
    if (bytes_read == 0) {
×
456
        std::cerr << "Read failed: 0x" << std::hex << pos << std::endl;
×
457
        memset(dst, 0x55, block_size);
×
458
        return;
×
459
    }
×
460

461
    iv_table& iv = get_iv_table(fd, pos, IVLookupMode::Refetch);
×
462
    if (iv.iv1 == 0) {
×
463
        std::cerr << "Block never written: 0x" << std::hex << pos << std::endl;
×
464
        memset(dst, 0xAA, block_size);
×
465
        return;
×
466
    }
×
467

468
    if (!check_hmac(m_rw_buffer.get(), bytes_read, iv.hmac1)) {
×
469
        if (iv.iv2 == 0) {
×
470
            std::cerr << "First write interrupted: 0x" << std::hex << pos << std::endl;
×
471
        }
×
472

473
        if (check_hmac(m_rw_buffer.get(), bytes_read, iv.hmac2)) {
×
474
            std::cerr << "Restore old IV: 0x" << std::hex << pos << std::endl;
×
475
            memcpy(&iv.iv1, &iv.iv2, 32);
×
476
        }
×
477
        else {
×
478
            std::cerr << "Checksum failed: 0x" << std::hex << pos << std::endl;
×
479
        }
×
480
    }
×
481
    crypt(mode_Decrypt, pos, dst, m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
×
482
}
×
483

484
void AESCryptor::write(FileDesc fd, off_t pos, const char* src, size_t size, WriteMarker* marker) noexcept
485
{
66,186✔
486
    REALM_ASSERT(size % block_size == 0);
66,186✔
487
    while (size > 0) {
174,852✔
488
        iv_table& iv = get_iv_table(fd, pos);
108,666✔
489

51,615✔
490
        memcpy(&iv.iv2, &iv.iv1, 32); // this is also copying the hmac
108,666✔
491
        do {
108,666✔
492
            ++iv.iv1;
108,666✔
493
            // 0 is reserved for never-been-used, so bump if we just wrapped around
51,615✔
494
            if (iv.iv1 == 0)
108,666✔
495
                ++iv.iv1;
×
496

51,615✔
497
            crypt(mode_Encrypt, pos, m_rw_buffer.get(), src, reinterpret_cast<const char*>(&iv.iv1));
108,666✔
498
            hmac_sha224(Span(reinterpret_cast<uint8_t*>(m_rw_buffer.get()), block_size), iv.hmac1, m_hmacKey);
108,666✔
499
            // In the extremely unlikely case that both the old and new versions have
51,615✔
500
            // the same hash we won't know which IV to use, so bump the IV until
51,615✔
501
            // they're different.
51,615✔
502
        } while (REALM_UNLIKELY(iv.hmac1 == iv.hmac2));
108,666✔
503

51,615✔
504
        if (marker)
108,666✔
505
            marker->mark(pos);
2,556✔
506
        check_write(fd, iv_table_pos(pos), &iv, sizeof(iv));
108,666✔
507
        check_write(fd, real_offset(pos), m_rw_buffer.get(), block_size);
108,666✔
508
        if (marker)
108,666✔
509
            marker->unmark();
2,556✔
510

51,615✔
511
        pos += block_size;
108,666✔
512
        src += block_size;
108,666✔
513
        size -= block_size;
108,666✔
514
    }
108,666✔
515
}
66,186✔
516

517
void AESCryptor::crypt(EncryptionMode mode, off_t pos, char* dst, const char* src, const char* stored_iv) noexcept
518
{
542,943✔
519
    uint8_t iv[aes_block_size] = {0};
542,943✔
520
    memcpy(iv, stored_iv, 4);
542,943✔
521
    memcpy(iv + 4, &pos, sizeof(pos));
542,943✔
522

259,899✔
523
#if REALM_PLATFORM_APPLE
283,044✔
524
    CCCryptorRef cryptor = mode == mode_Encrypt ? m_encr : m_decr;
225,993✔
525
    CCCryptorReset(cryptor, iv);
283,044✔
526

527
    size_t bytesEncrypted = 0;
283,044✔
528
    CCCryptorStatus err = CCCryptorUpdate(cryptor, src, block_size, dst, block_size, &bytesEncrypted);
283,044✔
529
    REALM_ASSERT(err == kCCSuccess);
283,044✔
530
    REALM_ASSERT(bytesEncrypted == block_size);
283,044✔
531
#elif defined(_WIN32)
532
    ULONG cbData;
533
    int i;
534

535
    if (mode == mode_Encrypt) {
536
        i = BCryptEncrypt(m_aes_key_handle, (PUCHAR)src, block_size, nullptr, (PUCHAR)iv, sizeof(iv), (PUCHAR)dst,
537
                          block_size, &cbData, 0);
538
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptEncrypt()", i);
539
        REALM_ASSERT_RELEASE_EX(cbData == block_size && "BCryptEncrypt()", cbData);
540
    }
541
    else if (mode == mode_Decrypt) {
542
        i = BCryptDecrypt(m_aes_key_handle, (PUCHAR)src, block_size, nullptr, (PUCHAR)iv, sizeof(iv), (PUCHAR)dst,
543
                          block_size, &cbData, 0);
544
        REALM_ASSERT_RELEASE_EX(i == 0 && "BCryptDecrypt()", i);
545
        REALM_ASSERT_RELEASE_EX(cbData == block_size && "BCryptDecrypt()", cbData);
546
    }
547
    else {
548
        REALM_UNREACHABLE();
549
    }
550

551
#else
552
    if (!EVP_CipherInit_ex(m_ctx, EVP_aes_256_cbc(), NULL, m_aesKey.data(), iv, mode))
259,899✔
553
        handle_error();
554

259,899✔
555
    int len;
259,899✔
556
    // Use zero padding - we always write a whole page
259,899✔
557
    EVP_CIPHER_CTX_set_padding(m_ctx, 0);
259,899✔
558

259,899✔
559
    if (!EVP_CipherUpdate(m_ctx, reinterpret_cast<uint8_t*>(dst), &len, reinterpret_cast<const uint8_t*>(src),
259,899✔
560
                          block_size))
259,899✔
561
        handle_error();
562

259,899✔
563
    // Finalize the encryption. Should not output further data.
259,899✔
564
    if (!EVP_CipherFinal_ex(m_ctx, reinterpret_cast<uint8_t*>(dst) + len, &len))
259,899✔
565
        handle_error();
566
#endif
259,899✔
567
}
542,943✔
568

569
EncryptedFileMapping::EncryptedFileMapping(SharedFileInfo& file, size_t file_offset, void* addr, size_t size,
570
                                           File::AccessMode access, util::WriteObserver* observer,
571
                                           util::WriteMarker* marker)
572
    : m_file(file)
573
    , m_page_shift(log2(realm::util::page_size()))
574
    , m_blocks_per_page(static_cast<size_t>(1ULL << m_page_shift) / block_size)
575
    , m_num_decrypted(0)
576
    , m_access(access)
577
    , m_observer(observer)
578
    , m_marker(marker)
579
#ifdef REALM_DEBUG
580
    , m_validate_buffer(new char[static_cast<size_t>(1ULL << m_page_shift)])
581
#endif
582
{
3,438✔
583
    REALM_ASSERT(m_blocks_per_page * block_size == static_cast<size_t>(1ULL << m_page_shift));
3,438✔
584
    set(addr, size, file_offset); // throws
3,438✔
585
    file.mappings.push_back(this);
3,438✔
586
}
3,438✔
587

588
EncryptedFileMapping::~EncryptedFileMapping()
589
{
3,438✔
590
    for (auto& e : m_page_state) {
2,482,548✔
591
        REALM_ASSERT(is_not(e, Writable));
2,482,548✔
592
    }
2,482,548✔
593
    if (m_access == File::access_ReadWrite) {
3,438✔
594
        flush();
2,685✔
595
        sync();
2,685✔
596
    }
2,685✔
597
    m_file.mappings.erase(remove(m_file.mappings.begin(), m_file.mappings.end(), this));
3,438✔
598
}
3,438✔
599

600
char* EncryptedFileMapping::page_addr(size_t local_page_ndx) const noexcept
601
{
444,132✔
602
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
444,132✔
603
    return static_cast<char*>(m_addr) + (local_page_ndx << m_page_shift);
444,132✔
604
}
444,132✔
605

606
void EncryptedFileMapping::mark_outdated(size_t local_page_ndx) noexcept
607
{
36✔
608
    if (local_page_ndx >= m_page_state.size())
36✔
609
        return;
×
610
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], UpToDate));
36✔
611
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Dirty));
36✔
612
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Writable));
36✔
613

30✔
614
    size_t chunk_ndx = local_page_ndx >> page_to_chunk_shift;
36✔
615
    if (m_chunk_dont_scan[chunk_ndx])
36✔
616
        m_chunk_dont_scan[chunk_ndx] = 0;
×
617
}
36✔
618

619
bool EncryptedFileMapping::copy_up_to_date_page(size_t local_page_ndx) noexcept
620
{
129,702✔
621
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
129,702✔
622
    // Precondition: this method must never be called for a page which
102,084✔
623
    // is already up to date.
102,084✔
624
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], UpToDate));
129,702✔
625
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
383,286✔
626
        EncryptedFileMapping* m = m_file.mappings[i];
254,427✔
627
        size_t page_ndx_in_file = local_page_ndx + m_first_page;
254,427✔
628
        if (m == this || !m->contains_page(page_ndx_in_file))
254,427✔
629
            continue;
253,422✔
630

534✔
631
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
1,005✔
632
        if (is(m->m_page_state[shadow_mapping_local_ndx], UpToDate)) {
1,005✔
633
            memcpy(page_addr(local_page_ndx), m->page_addr(shadow_mapping_local_ndx),
843✔
634
                   static_cast<size_t>(1ULL << m_page_shift));
843✔
635
            return true;
843✔
636
        }
843✔
637
    }
1,005✔
638
    return false;
129,309✔
639
}
129,702✔
640

641
void EncryptedFileMapping::refresh_page(size_t local_page_ndx, size_t required)
642
{
129,702✔
643
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
129,702✔
644
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Dirty));
129,702✔
645
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], Writable));
129,702✔
646
    char* addr = page_addr(local_page_ndx);
129,702✔
647

102,084✔
648
    if (!copy_up_to_date_page(local_page_ndx)) {
129,702✔
649
        const size_t page_ndx_in_file = local_page_ndx + m_first_page;
128,859✔
650
        const size_t end_page_ndx_in_file = m_first_page + m_page_state.size();
128,859✔
651
        off_t data_pos = off_t(page_ndx_in_file << m_page_shift);
128,859✔
652
        if (is(m_page_state[local_page_ndx], StaleIV)) {
128,859✔
653
            auto refreshed_ivs =
1,014✔
654
                m_file.cryptor.refresh_ivs(m_file.fd, data_pos, page_ndx_in_file, end_page_ndx_in_file);
1,014✔
655
            for (const auto& [page_ndx, state] : refreshed_ivs) {
3,915✔
656
                size_t local_page_ndx_of_iv_change = page_ndx - m_first_page;
3,915✔
657
                REALM_ASSERT_EX(contains_page(page_ndx), page_ndx, m_first_page, m_page_state.size());
3,915✔
658
                if (is(m_page_state[local_page_ndx_of_iv_change], Dirty | Writable)) {
3,915✔
659
                    continue;
×
660
                }
×
661
                switch (state) {
3,915✔
662
                    case IVRefreshState::UpToDate:
1,431✔
663
                        if (is(m_page_state[local_page_ndx_of_iv_change], StaleIV)) {
1,431✔
664
                            set(m_page_state[local_page_ndx_of_iv_change], UpToDate);
669✔
665
                            clear(m_page_state[local_page_ndx_of_iv_change], StaleIV);
669✔
666
                        }
669✔
667
                        break;
1,431✔
668
                    case IVRefreshState::RequiresRefresh:
2,484✔
669
                        clear(m_page_state[local_page_ndx_of_iv_change], StaleIV);
2,484✔
670
                        clear(m_page_state[local_page_ndx_of_iv_change], UpToDate);
2,484✔
671
                        break;
2,484✔
672
                }
3,915✔
673
            }
3,915✔
674
            REALM_ASSERT_EX(refreshed_ivs.count(page_ndx_in_file) == 1, page_ndx_in_file, refreshed_ivs.size());
1,014✔
675
            if (refreshed_ivs[page_ndx_in_file] == IVRefreshState::UpToDate) {
1,014✔
676
                return;
549✔
677
            }
549✔
678
        }
128,310✔
679
        size_t size = static_cast<size_t>(1ULL << m_page_shift);
128,310✔
680
        size_t actual = m_file.cryptor.read(m_file.fd, data_pos, addr, size, m_observer);
128,310✔
681
        if (actual < size) {
128,310✔
682
            if (actual >= required) {
63,456✔
683
                memset(addr + actual, 0x55, size - actual);
63,450✔
684
            }
63,450✔
685
            else {
6✔
686
                throw DecryptionFailed();
6✔
687
            }
6✔
688
        }
129,147✔
689
    }
128,310✔
690
    if (is_not(m_page_state[local_page_ndx], UpToDate))
129,147✔
691
        m_num_decrypted++;
129,123✔
692
    set(m_page_state[local_page_ndx], UpToDate);
129,147✔
693
    clear(m_page_state[local_page_ndx], StaleIV);
129,147✔
694
}
129,147✔
695

696
void EncryptedFileMapping::mark_pages_for_IV_check()
697
{
1,554✔
698
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
3,222✔
699
        EncryptedFileMapping* m = m_file.mappings[i];
1,668✔
700
        for (size_t pg = m->get_start_index(); pg < m->get_end_index(); ++pg) {
507,279✔
701
            size_t local_page_ndx = pg - m->m_first_page;
505,611✔
702
            if (is(m->m_page_state[local_page_ndx], UpToDate) &&
505,611✔
703
                is_not(m->m_page_state[local_page_ndx], Dirty | Writable)) {
404,667✔
704
                REALM_ASSERT(is_not(m->m_page_state[local_page_ndx], StaleIV));
1,320✔
705
                clear(m->m_page_state[local_page_ndx], UpToDate);
1,320✔
706
                set(m->m_page_state[local_page_ndx], StaleIV);
1,320✔
707
            }
1,320✔
708
        }
505,611✔
709
    }
1,668✔
710
}
1,554✔
711

712
void EncryptedFileMapping::write_and_update_all(size_t local_page_ndx, size_t begin_offset,
713
                                                size_t end_offset) noexcept
714
{
75,234✔
715
    REALM_ASSERT(is(m_page_state[local_page_ndx], Writable));
75,234✔
716
    REALM_ASSERT(is(m_page_state[local_page_ndx], UpToDate));
75,234✔
717
    // Go through all other mappings of this file and copy changes into those mappings
56,121✔
718
    size_t page_ndx_in_file = local_page_ndx + m_first_page;
75,234✔
719
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
225,477✔
720
        EncryptedFileMapping* m = m_file.mappings[i];
150,243✔
721
        if (m != this && m->contains_page(page_ndx_in_file)) {
150,243✔
722
            size_t shadow_local_page_ndx = page_ndx_in_file - m->m_first_page;
10,662✔
723
            if (is(m->m_page_state[shadow_local_page_ndx], UpToDate) ||
10,662✔
724
                is(m->m_page_state[shadow_local_page_ndx], StaleIV)) { // only keep up to data pages up to date
10,656✔
725
                memcpy(m->page_addr(shadow_local_page_ndx) + begin_offset, page_addr(local_page_ndx) + begin_offset,
10,626✔
726
                       end_offset - begin_offset);
10,626✔
727
                if (is(m->m_page_state[shadow_local_page_ndx], StaleIV)) {
10,626✔
728
                    set(m->m_page_state[shadow_local_page_ndx], UpToDate);
108✔
729
                    clear(m->m_page_state[shadow_local_page_ndx], StaleIV);
108✔
730
                }
108✔
731
            }
10,626✔
732
            else {
36✔
733
                m->mark_outdated(shadow_local_page_ndx);
36✔
734
            }
36✔
735
        }
10,662✔
736
    }
150,243✔
737
    set(m_page_state[local_page_ndx], Dirty);
75,234✔
738
    clear(m_page_state[local_page_ndx], Writable);
75,234✔
739
    clear(m_page_state[local_page_ndx], StaleIV);
75,234✔
740
    size_t chunk_ndx = local_page_ndx >> page_to_chunk_shift;
75,234✔
741
    if (m_chunk_dont_scan[chunk_ndx])
75,234✔
742
        m_chunk_dont_scan[chunk_ndx] = 0;
×
743
}
75,234✔
744

745

746
void EncryptedFileMapping::validate_page(size_t local_page_ndx) noexcept
747
{
10,812,432✔
748
#ifdef REALM_DEBUG
10,812,432✔
749
    REALM_ASSERT(local_page_ndx < m_page_state.size());
10,812,432✔
750
    if (is_not(m_page_state[local_page_ndx], UpToDate))
10,812,432✔
751
        return;
10,612,536✔
752

157,386✔
753
    const size_t page_ndx_in_file = local_page_ndx + m_first_page;
199,896✔
754
    if (!m_file.cryptor.read(m_file.fd, off_t(page_ndx_in_file << m_page_shift), m_validate_buffer.get(),
199,896✔
755
                             static_cast<size_t>(1ULL << m_page_shift), m_observer))
199,896✔
756
        return;
×
757

157,386✔
758
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
600,648✔
759
        EncryptedFileMapping* m = m_file.mappings[i];
400,752✔
760
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
400,752✔
761
        if (m != this && m->contains_page(page_ndx_in_file) && is(m->m_page_state[shadow_mapping_local_ndx], Dirty)) {
400,752✔
762
            memcpy(m_validate_buffer.get(), m->page_addr(shadow_mapping_local_ndx),
×
763
                   static_cast<size_t>(1ULL << m_page_shift));
×
764
            break;
×
765
        }
×
766
    }
400,752✔
767

157,386✔
768
    if (memcmp(m_validate_buffer.get(), page_addr(local_page_ndx), static_cast<size_t>(1ULL << m_page_shift))) {
199,896✔
769
        std::cerr << "mismatch " << this << ": fd(" << m_file.fd << ")"
×
770
                  << "page(" << local_page_ndx << "/" << m_page_state.size() << ") " << m_validate_buffer.get() << " "
×
771
                  << page_addr(local_page_ndx) << std::endl;
×
772
        REALM_TERMINATE("");
773
    }
×
774
#else
775
    static_cast<void>(local_page_ndx);
776
#endif
777
}
199,896✔
778

779
void EncryptedFileMapping::validate() noexcept
780
{
8,757✔
781
#ifdef REALM_DEBUG
8,757✔
782
    const size_t num_local_pages = m_page_state.size();
8,757✔
783
    for (size_t local_page_ndx = 0; local_page_ndx < num_local_pages; ++local_page_ndx)
5,447,640✔
784
        validate_page(local_page_ndx);
5,438,883✔
785
#endif
8,757✔
786
}
8,757✔
787

788
void EncryptedFileMapping::reclaim_page(size_t page_ndx)
789
{
×
790
#ifdef _WIN32
791
    // On windows we don't know how to replace a page within a page range with a fresh one.
792
    // instead we clear it. If the system runs with same-page-merging, this will reduce
793
    // the number of used pages.
794
    memset(page_addr(page_ndx), 0, static_cast<size_t>(1) << m_page_shift);
795
#else
796
    // On Posix compatible, we can request a new page in the middle of an already
797
    // requested range, so that's what we do. This releases the backing store for the
798
    // old page and gives us a shared zero-page that we can later demand-allocate, thus
799
    // reducing the overall amount of used physical pages.
800
    void* addr = page_addr(page_ndx);
×
801
    void* addr2 = ::mmap(addr, 1 << m_page_shift, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
×
802
    if (addr != addr2) {
×
803
        if (addr2 == 0) {
×
804
            int err = errno;
×
805
            throw SystemError(err, get_errno_msg("using mmap() to clear page failed", err));
×
806
        }
×
807
        throw std::runtime_error("internal error in mmap()");
×
808
    }
×
809
#endif
×
810
}
×
811

812
/* This functions is a bit convoluted. It reclaims pages, but only does a limited amount of work
813
 * each time it's called. It saves the progress in a 'progress_ptr' so that it can resume later
814
 * from where it was stopped.
815
 *
816
 * The workload is composed of workunits, each unit signifying
817
 * 1) A scanning of the state of 4K pages
818
 * 2) One system call (to mmap to release a page and get a new one)
819
 * 3) A scanning of 1K entries in the "don't scan" array (corresponding to 4M pages)
820
 * Approximately
821
 */
822
void EncryptedFileMapping::reclaim_untouched(size_t& progress_index, size_t& work_limit) noexcept
UNCOV
823
{
×
UNCOV
824
    const auto scan_amount_per_workunit = 4096;
×
UNCOV
825
    bool contiguous_scan = false;
×
UNCOV
826
    size_t next_scan_payment = scan_amount_per_workunit;
×
UNCOV
827
    const size_t last_index = get_end_index();
×
828

UNCOV
829
    auto done_some_work = [&]() {
×
UNCOV
830
        if (work_limit > 0)
×
UNCOV
831
            work_limit--;
×
UNCOV
832
    };
×
833

UNCOV
834
    auto visit_and_potentially_reclaim = [&](size_t page_ndx) {
×
UNCOV
835
        PageState& ps = m_page_state[page_ndx];
×
UNCOV
836
        if (is(ps, UpToDate)) {
×
UNCOV
837
            if (is_not(ps, Touched) && is_not(ps, Dirty) && is_not(ps, Writable)) {
×
838
                clear(ps, UpToDate);
×
839
                reclaim_page(page_ndx);
×
840
                m_num_decrypted--;
×
841
                done_some_work();
×
842
            }
×
UNCOV
843
            contiguous_scan = false;
×
UNCOV
844
        }
×
UNCOV
845
        clear(ps, Touched);
×
UNCOV
846
    };
×
847

UNCOV
848
    auto skip_chunk_if_possible = [&](size_t& page_ndx) // update vars corresponding to skipping a chunk if possible
×
UNCOV
849
    {
×
UNCOV
850
        size_t chunk_ndx = page_ndx >> page_to_chunk_shift;
×
UNCOV
851
        if (m_chunk_dont_scan[chunk_ndx]) {
×
852
            // skip to end of chunk
853
            page_ndx = ((chunk_ndx + 1) << page_to_chunk_shift) - 1;
×
854
            progress_index = m_first_page + page_ndx;
×
855
            // postpone next scan payment
856
            next_scan_payment += page_to_chunk_factor;
×
857
            return true;
×
858
        }
×
UNCOV
859
        else
×
UNCOV
860
            return false;
×
UNCOV
861
    };
×
862

UNCOV
863
    auto is_last_page_in_chunk = [](size_t page_ndx) {
×
UNCOV
864
        auto page_to_chunk_mask = page_to_chunk_factor - 1;
×
UNCOV
865
        return (page_ndx & page_to_chunk_mask) == page_to_chunk_mask;
×
UNCOV
866
    };
×
UNCOV
867
    auto is_first_page_in_chunk = [](size_t page_ndx) {
×
UNCOV
868
        auto page_to_chunk_mask = page_to_chunk_factor - 1;
×
UNCOV
869
        return (page_ndx & page_to_chunk_mask) == 0;
×
UNCOV
870
    };
×
871

UNCOV
872
    while (work_limit > 0 && progress_index < last_index) {
×
UNCOV
873
        size_t page_ndx = progress_index - m_first_page;
×
UNCOV
874
        if (!skip_chunk_if_possible(page_ndx)) {
×
UNCOV
875
            if (is_first_page_in_chunk(page_ndx)) {
×
UNCOV
876
                contiguous_scan = true;
×
UNCOV
877
            }
×
UNCOV
878
            visit_and_potentially_reclaim(page_ndx);
×
879
            // if we've scanned a full chunk contiguously, mark it as not needing scans
UNCOV
880
            if (is_last_page_in_chunk(page_ndx)) {
×
UNCOV
881
                if (contiguous_scan) {
×
882
                    m_chunk_dont_scan[page_ndx >> page_to_chunk_shift] = 1;
×
883
                }
×
UNCOV
884
                contiguous_scan = false;
×
UNCOV
885
            }
×
UNCOV
886
        }
×
887
        // account for work performed:
UNCOV
888
        if (page_ndx >= next_scan_payment) {
×
UNCOV
889
            next_scan_payment = page_ndx + scan_amount_per_workunit;
×
UNCOV
890
            done_some_work();
×
UNCOV
891
        }
×
UNCOV
892
        ++progress_index;
×
UNCOV
893
    }
×
UNCOV
894
    return;
×
UNCOV
895
}
×
896

897
void EncryptedFileMapping::flush() noexcept
898
{
8,757✔
899
    const size_t num_dirty_pages = m_page_state.size();
8,757✔
900
    for (size_t local_page_ndx = 0; local_page_ndx < num_dirty_pages; ++local_page_ndx) {
5,447,640✔
901
        if (is_not(m_page_state[local_page_ndx], Dirty)) {
5,438,883✔
902
            validate_page(local_page_ndx);
5,373,549✔
903
            continue;
5,373,549✔
904
        }
5,373,549✔
905

51,180✔
906
        size_t page_ndx_in_file = local_page_ndx + m_first_page;
65,334✔
907
        m_file.cryptor.write(m_file.fd, off_t(page_ndx_in_file << m_page_shift), page_addr(local_page_ndx),
65,334✔
908
                             static_cast<size_t>(1ULL << m_page_shift), m_marker);
65,334✔
909
        clear(m_page_state[local_page_ndx], Dirty);
65,334✔
910
    }
65,334✔
911

4,188✔
912
    validate();
8,757✔
913
}
8,757✔
914

915
#ifdef _MSC_VER
916
#pragma warning(disable : 4297) // throw in noexcept
917
#endif
918
void EncryptedFileMapping::sync() noexcept
919
{
4,275✔
920
#ifdef _WIN32
921
    if (FlushFileBuffers(m_file.fd))
922
        return;
923
    throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
924
#else
925
    fsync(m_file.fd);
4,275✔
926
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
2,052✔
927
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
2,052✔
928
    //
2,052✔
929
    // See description of fsync on iOS here:
2,052✔
930
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
2,052✔
931
    //
2,052✔
932
    // See also
2,052✔
933
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
2,052✔
934
    // for a discussion of this related to core data.
2,052✔
935
#endif
4,275✔
936
}
4,275✔
937
#ifdef _MSC_VER
938
#pragma warning(default : 4297)
939
#endif
940

941
void EncryptedFileMapping::write_barrier(const void* addr, size_t size) noexcept
942
{
13,131✔
943
    // Propagate changes to all other decrypted pages mapping the same memory
6,477✔
944

6,477✔
945
    REALM_ASSERT(m_access == File::access_ReadWrite);
13,131✔
946
    size_t first_accessed_local_page = get_local_index_of_address(addr);
13,131✔
947
    size_t first_offset = static_cast<const char*>(addr) - page_addr(first_accessed_local_page);
13,131✔
948
    const char* last_accessed_address = static_cast<const char*>(addr) + (size == 0 ? 0 : size - 1);
13,131✔
949
    size_t last_accessed_local_page = get_local_index_of_address(last_accessed_address);
13,131✔
950
    size_t pages_size = m_page_state.size();
13,131✔
951

6,477✔
952
    // propagate changes to first page (update may be partial, may also be to last page)
6,477✔
953
    if (first_accessed_local_page < pages_size) {
13,131✔
954
        REALM_ASSERT_EX(is(m_page_state[first_accessed_local_page], UpToDate),
13,131✔
955
                        m_page_state[first_accessed_local_page]);
13,131✔
956
        if (first_accessed_local_page == last_accessed_local_page) {
13,131✔
957
            size_t last_offset = last_accessed_address - page_addr(first_accessed_local_page);
12,252✔
958
            write_and_update_all(first_accessed_local_page, first_offset, last_offset + 1);
12,252✔
959
        }
12,252✔
960
        else
879✔
961
            write_and_update_all(first_accessed_local_page, first_offset, static_cast<size_t>(1) << m_page_shift);
879✔
962
    }
13,131✔
963
    // propagate changes to pages between first and last page (update only full pages)
6,477✔
964
    for (size_t idx = first_accessed_local_page + 1; idx < last_accessed_local_page && idx < pages_size; ++idx) {
74,355✔
965
        REALM_ASSERT(is(m_page_state[idx], UpToDate));
61,224✔
966
        write_and_update_all(idx, 0, static_cast<size_t>(1) << m_page_shift);
61,224✔
967
    }
61,224✔
968
    // propagate changes to the last page (update may be partial)
6,477✔
969
    if (first_accessed_local_page < last_accessed_local_page && last_accessed_local_page < pages_size) {
13,131✔
970
        REALM_ASSERT(is(m_page_state[last_accessed_local_page], UpToDate));
879✔
971
        size_t last_offset = last_accessed_address - page_addr(last_accessed_local_page);
879✔
972
        write_and_update_all(last_accessed_local_page, 0, last_offset + 1);
879✔
973
    }
879✔
974
}
13,131✔
975

976
void EncryptedFileMapping::read_barrier(const void* addr, size_t size, Header_to_size header_to_size, bool to_modify)
977
{
1,256,973✔
978
    size_t first_accessed_local_page = get_local_index_of_address(addr);
1,256,973✔
979
    size_t page_size = 1ULL << m_page_shift;
1,256,973✔
980
    size_t required = get_offset_of_address(addr) + size;
1,256,973✔
981
    {
1,256,973✔
982
        // make sure the first page is available
662,265✔
983
        PageState& ps = m_page_state[first_accessed_local_page];
1,256,973✔
984
        if (is_not(ps, Touched))
1,256,973✔
985
            set(ps, Touched);
4,497✔
986
        if (is_not(ps, UpToDate))
1,256,973✔
987
            refresh_page(first_accessed_local_page, to_modify ? 0 : required);
5,550✔
988
        if (to_modify)
1,256,973✔
989
            set(ps, Writable);
13,131✔
990
    }
1,256,973✔
991

662,265✔
992
    // force the page reclaimer to look into pages in this chunk:
662,265✔
993
    size_t chunk_ndx = first_accessed_local_page >> page_to_chunk_shift;
1,256,973✔
994
    if (m_chunk_dont_scan[chunk_ndx])
1,256,973✔
995
        m_chunk_dont_scan[chunk_ndx] = 0;
×
996

662,265✔
997
    if (header_to_size) {
1,256,973✔
998
        // We know it's an array, and array headers are 8-byte aligned, so it is
654,138✔
999
        // included in the first page which was handled above.
654,138✔
1000
        size = header_to_size(static_cast<const char*>(addr));
1,240,611✔
1001
        required = get_offset_of_address(addr) + size;
1,240,611✔
1002
    }
1,240,611✔
1003

662,265✔
1004
    size_t last_idx = get_local_index_of_address(addr, size == 0 ? 0 : size - 1);
1,256,973✔
1005
    size_t pages_size = m_page_state.size();
1,256,973✔
1006

662,265✔
1007
    // We already checked first_accessed_local_page above, so we start the loop
662,265✔
1008
    // at first_accessed_local_page + 1 to check the following page.
662,265✔
1009
    for (size_t idx = first_accessed_local_page + 1; idx <= last_idx && idx < pages_size; ++idx) {
1,526,370✔
1010
        required -= page_size;
269,397✔
1011
        // force the page reclaimer to look into pages in this chunk
232,242✔
1012
        chunk_ndx = idx >> page_to_chunk_shift;
269,397✔
1013
        if (m_chunk_dont_scan[chunk_ndx])
269,397✔
1014
            m_chunk_dont_scan[chunk_ndx] = 0;
×
1015

232,242✔
1016
        PageState& ps = m_page_state[idx];
269,397✔
1017
        if (is_not(ps, Touched))
269,397✔
1018
            set(ps, Touched);
124,152✔
1019
        if (is_not(ps, UpToDate))
269,397✔
1020
            refresh_page(idx, to_modify ? 0 : required);
124,152✔
1021
        if (to_modify)
269,397✔
1022
            set(ps, Writable);
62,103✔
1023
    }
269,397✔
1024
}
1,256,973✔
1025

1026
void EncryptedFileMapping::extend_to(size_t offset, size_t new_size)
1027
{
327✔
1028
    REALM_ASSERT(new_size % (1ULL << m_page_shift) == 0);
327✔
1029
    size_t num_pages = new_size >> m_page_shift;
327✔
1030
    m_page_state.resize(num_pages, PageState::Clean);
327✔
1031
    m_chunk_dont_scan.resize((num_pages + page_to_chunk_factor - 1) >> page_to_chunk_shift, false);
327✔
1032
    m_file.cryptor.set_file_size((off_t)(offset + new_size));
327✔
1033
}
327✔
1034

1035
void EncryptedFileMapping::set(void* new_addr, size_t new_size, size_t new_file_offset)
1036
{
3,438✔
1037
    REALM_ASSERT(new_file_offset % (1ULL << m_page_shift) == 0);
3,438✔
1038
    REALM_ASSERT(new_size % (1ULL << m_page_shift) == 0);
3,438✔
1039

1,587✔
1040
    // This seems dangerous - correct operation in a setting with multiple (partial)
1,587✔
1041
    // mappings of the same file would rely on ordering of individual mapping requests.
1,587✔
1042
    // Currently we only ever extend the file - but when we implement continuous defrag,
1,587✔
1043
    // this design should be revisited.
1,587✔
1044
    m_file.cryptor.set_file_size(off_t(new_size + new_file_offset));
3,438✔
1045

1,587✔
1046
    flush();
3,438✔
1047
    m_addr = new_addr;
3,438✔
1048

1,587✔
1049
    m_first_page = new_file_offset >> m_page_shift;
3,438✔
1050
    size_t num_pages = new_size >> m_page_shift;
3,438✔
1051

1,587✔
1052
    m_num_decrypted = 0;
3,438✔
1053
    m_page_state.clear();
3,438✔
1054
    m_chunk_dont_scan.clear();
3,438✔
1055

1,587✔
1056
    m_page_state.resize(num_pages, PageState(0));
3,438✔
1057
    m_chunk_dont_scan.resize((num_pages + page_to_chunk_factor - 1) >> page_to_chunk_shift, false);
3,438✔
1058
}
3,438✔
1059

1060
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1061
{
4,266✔
1062
    if (size == 0)
4,266✔
1063
        return 0;
114✔
1064
    return fake_offset(size);
4,152✔
1065
}
4,152✔
1066

1067
File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept
1068
{
513✔
1069
    size_t ps = page_size();
513✔
1070
    return real_offset((size + ps - 1) & ~(ps - 1));
513✔
1071
}
513✔
1072
} // namespace realm::util
1073
#else
1074

1075
namespace realm::util {
1076
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1077
{
1078
    return size;
1079
}
1080

1081
File::SizeType data_size_to_encrypted_size(File::SizeType size) noexcept
1082
{
1083
    return size;
1084
}
1085
} // namespace realm::util
1086
#endif // REALM_ENABLE_ENCRYPTION
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc