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

realm / realm-core / 1741

06 Oct 2023 04:08PM UTC coverage: 91.583% (-0.03%) from 91.61%
1741

push

Evergreen

realm-ci
Updated release notes

94296 of 173524 branches covered (0.0%)

230550 of 251738 relevant lines covered (91.58%)

6663918.53 hits per line

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

83.12
/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
{
1,980✔
60
}
1,980✔
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,313✔
91
        return iv1 == other.iv1 && iv2 == other.iv2 && hmac1 == other.hmac1 && hmac2 == other.hmac2;
20,313✔
92
    }
20,313✔
93
    bool operator!=(const iv_table& other) const
94
    {
12,588✔
95
        return !(*this == other);
12,588✔
96
    }
12,588✔
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,921✔
110
    REALM_ASSERT(pos >= 0);
606,921✔
111
    const size_t index = static_cast<size_t>(pos) / block_size;
606,921✔
112
    const size_t metadata_page_count = index / blocks_per_metadata_block + 1;
606,921✔
113
    return Int(pos + metadata_page_count * block_size);
606,921✔
114
}
606,921✔
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,053✔
120
    REALM_ASSERT(pos >= 0);
4,053✔
121
    const size_t index = static_cast<size_t>(pos) / block_size;
4,053✔
122
    const size_t metadata_page_count = (index + blocks_per_metadata_block) / (blocks_per_metadata_block + 1);
4,053✔
123
    return pos - metadata_page_count * block_size;
4,053✔
124
}
4,053✔
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,359✔
129
    REALM_ASSERT(pos >= 0);
115,359✔
130
    const size_t index = static_cast<size_t>(pos) / block_size;
115,359✔
131
    const size_t metadata_block = index / blocks_per_metadata_block;
115,359✔
132
    const size_t metadata_index = index & (blocks_per_metadata_block - 1);
115,359✔
133
    return off_t(metadata_block * (blocks_per_metadata_block + 1) * block_size + metadata_index * metadata_size);
115,359✔
134
}
115,359✔
135

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

144
size_t check_read(FileDesc fd, off_t pos, void* dst, size_t len)
145
{
504,417✔
146
    uint64_t orig = File::get_file_pos(fd);
504,417✔
147
    File::seek_static(fd, pos);
504,417✔
148
    size_t ret = File::read_static(fd, static_cast<char*>(dst), len);
504,417✔
149
    File::seek_static(fd, orig);
504,417✔
150
    return ret;
504,417✔
151
}
504,417✔
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,076✔
159
    memcpy(m_aesKey.data(), key, 32);
2,076✔
160
    memcpy(m_hmacKey.data(), key + 32, 32);
2,076✔
161

867✔
162
#if REALM_PLATFORM_APPLE
1,209✔
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,209✔
167
    arc4random_buf(u_iv, kCCKeySizeAES256);
1,209✔
168
    void* iv = u_iv;
1,209✔
169
    CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_encr);
1,209✔
170
    CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, 0 /* options */, key, kCCKeySizeAES256, iv, &m_decr);
1,209✔
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();
867✔
185
    if (!m_ctx)
867✔
186
        handle_error();
187
#endif
867✔
188
}
2,076✔
189

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

202
void AESCryptor::check_key(const uint8_t* key)
203
{
1,359✔
204
    if (memcmp(m_aesKey.data(), key, 32) != 0 || memcmp(m_hmacKey.data(), key + 32, 32) != 0)
1,359✔
205
        throw DecryptionFailed();
6✔
206
}
1,359✔
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,732✔
215
    REALM_ASSERT(new_size >= 0 && !int_cast_has_overflow<size_t>(new_size));
3,732✔
216
    size_t new_size_casted = size_t(new_size);
3,732✔
217
    size_t block_count = (new_size_casted + block_size - 1) / block_size;
3,732✔
218
    m_iv_buffer.reserve((block_count + blocks_per_metadata_block - 1) & ~(blocks_per_metadata_block - 1));
3,732✔
219
    m_iv_buffer_cache.reserve(m_iv_buffer.capacity());
3,732✔
220
}
3,732✔
221

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

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

2,952✔
238
    for (size_t i = block_start; i < block_end * blocks_per_metadata_block; i += blocks_per_metadata_block) {
12,825✔
239
        off_t iv_pos = iv_table_pos(off_t(i * block_size));
6,684✔
240
        size_t bytes = check_read(fd, iv_pos, &m_iv_buffer[i], block_size);
6,684✔
241
        if (bytes < block_size)
6,684✔
242
            break; // rest is zero-filled by resize()
189✔
243
    }
6,684✔
244

2,952✔
245
    return m_iv_buffer[idx];
6,330✔
246
}
6,330✔
247

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

207,858✔
253
    // Constant-time memcmp to avoid timing attacks
207,858✔
254
    uint8_t result = 0;
434,346✔
255
    for (size_t i = 0; i < 224 / 8; ++i)
12,596,034✔
256
        result |= buffer[i] ^ hmac[i];
12,161,688✔
257
    return result == 0;
434,346✔
258
}
434,346✔
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,050✔
263
    REALM_ASSERT_EX(page_ndx_in_file_expected < end_page_ndx_in_file, page_ndx_in_file_expected,
1,050✔
264
                    end_page_ndx_in_file);
1,050✔
265
    // the indices returned are page indices, not block indices
510✔
266
    util::FlatMap<size_t, IVRefreshState> page_states;
1,050✔
267

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

510✔
278
    get_iv_table(fd, data_pos, IVLookupMode::Refetch);
1,050✔
279

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

314
size_t AESCryptor::read(FileDesc fd, off_t pos, char* dst, size_t size, WriteObserver* observer)
315
{
327,879✔
316
    REALM_ASSERT_EX(size % block_size == 0, size, block_size);
327,879✔
317
    // We need to throw DecryptionFailed if the key is incorrect or there has been a corruption in the data but
258,267✔
318
    // not in a reader starvation scenario where a different process is writing pages and ivs faster than we can read
258,267✔
319
    // them. We also want to optimize for a single process writer since in that case all the cached ivs are correct.
258,267✔
320
    // To do this, we first attempt to use the cached IV, and if it is invalid, read from disk again. During reader
258,267✔
321
    // starvation, the just read IV could already be out of date with the data page, so continue trying to read until
258,267✔
322
    // a match is found (for up to 5 seconds before giving up entirely).
258,267✔
323
    size_t retry_count = 0;
327,879✔
324
    std::pair<iv_table, size_t> last_iv_and_data_hash;
327,879✔
325
    auto retry_start_time = std::chrono::steady_clock::now();
327,879✔
326
    size_t num_identical_reads = 1;
327,879✔
327
    auto retry = [&](std::string_view page_data, const iv_table& iv, const char* debug_from) {
258,315✔
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,267✔
358
    auto should_retry = [&]() -> bool {
270,987✔
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,267✔
371
    size_t bytes_read = 0;
327,879✔
372
    while (bytes_read < size) {
762,159✔
373
        ssize_t actual = check_read(fd, real_offset(pos), m_rw_buffer.get(), block_size);
497,733✔
374

258,300✔
375
        if (actual == 0)
497,733✔
376
            return bytes_read;
837✔
377

257,727✔
378
        iv_table& iv = get_iv_table(fd, pos, retry_count == 0 ? IVLookupMode::UseCache : IVLookupMode::Refetch);
496,896✔
379
        if (iv.iv1 == 0) {
496,896✔
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

207,819✔
390
        if (!check_hmac(m_rw_buffer.get(), actual, iv.hmac1)) {
434,268✔
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

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

207,783✔
443
        pos += block_size;
434,196✔
444
        dst += block_size;
434,196✔
445
        bytes_read += block_size;
434,196✔
446
        retry_count = 0;
434,196✔
447
    }
434,196✔
448
    return bytes_read;
314,907✔
449
}
327,879✔
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,114✔
486
    REALM_ASSERT(size % block_size == 0);
66,114✔
487
    while (size > 0) {
174,789✔
488
        iv_table& iv = get_iv_table(fd, pos);
108,675✔
489

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

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

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

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

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

259,299✔
523
#if REALM_PLATFORM_APPLE
283,572✔
524
    CCCryptorRef cryptor = mode == mode_Encrypt ? m_encr : m_decr;
226,413✔
525
    CCCryptorReset(cryptor, iv);
283,572✔
526

527
    size_t bytesEncrypted = 0;
283,572✔
528
    CCCryptorStatus err = CCCryptorUpdate(cryptor, src, block_size, dst, block_size, &bytesEncrypted);
283,572✔
529
    REALM_ASSERT(err == kCCSuccess);
283,572✔
530
    REALM_ASSERT(bytesEncrypted == block_size);
283,572✔
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,299✔
553
        handle_error();
554

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

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

259,299✔
563
    // Finalize the encryption. Should not output further data.
259,299✔
564
    if (!EVP_CipherFinal_ex(m_ctx, reinterpret_cast<uint8_t*>(dst) + len, &len))
259,299✔
565
        handle_error();
566
#endif
259,299✔
567
}
542,871✔
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,333✔
583
    REALM_ASSERT(m_blocks_per_page * block_size == static_cast<size_t>(1ULL << m_page_shift));
3,333✔
584
    set(addr, size, file_offset); // throws
3,333✔
585
    file.mappings.push_back(this);
3,333✔
586
}
3,333✔
587

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

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

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

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

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

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

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

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

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

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

745

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

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

156,963✔
758
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
599,607✔
759
        EncryptedFileMapping* m = m_file.mappings[i];
400,017✔
760
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
400,017✔
761
        if (m != this && m->contains_page(page_ndx_in_file) && is(m->m_page_state[shadow_mapping_local_ndx], Dirty)) {
400,017✔
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,017✔
767

156,963✔
768
    if (memcmp(m_validate_buffer.get(), page_addr(local_page_ndx), static_cast<size_t>(1ULL << m_page_shift))) {
199,590✔
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,590✔
778

779
void EncryptedFileMapping::validate() noexcept
780
{
8,481✔
781
#ifdef REALM_DEBUG
8,481✔
782
    const size_t num_local_pages = m_page_state.size();
8,481✔
783
    for (size_t local_page_ndx = 0; local_page_ndx < num_local_pages; ++local_page_ndx)
5,447,169✔
784
        validate_page(local_page_ndx);
5,438,688✔
785
#endif
8,481✔
786
}
8,481✔
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
823
{
×
824
    const auto scan_amount_per_workunit = 4096;
×
825
    bool contiguous_scan = false;
×
826
    size_t next_scan_payment = scan_amount_per_workunit;
×
827
    const size_t last_index = get_end_index();
×
828

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

834
    auto visit_and_potentially_reclaim = [&](size_t page_ndx) {
×
835
        PageState& ps = m_page_state[page_ndx];
×
836
        if (is(ps, UpToDate)) {
×
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
            }
×
843
            contiguous_scan = false;
×
844
        }
×
845
        clear(ps, Touched);
×
846
    };
×
847

848
    auto skip_chunk_if_possible = [&](size_t& page_ndx) // update vars corresponding to skipping a chunk if possible
×
849
    {
×
850
        size_t chunk_ndx = page_ndx >> page_to_chunk_shift;
×
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
        }
×
859
        else
×
860
            return false;
×
861
    };
×
862

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

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

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

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

3,843✔
912
    validate();
8,481✔
913
}
8,481✔
914

915
#ifdef _MSC_VER
916
#pragma warning(disable : 4297) // throw in noexcept
917
#endif
918
void EncryptedFileMapping::sync() noexcept
919
{
4,167✔
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,167✔
926
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
1,899✔
927
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
1,899✔
928
    //
1,899✔
929
    // See description of fsync on iOS here:
1,899✔
930
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
1,899✔
931
    //
1,899✔
932
    // See also
1,899✔
933
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
1,899✔
934
    // for a discussion of this related to core data.
1,899✔
935
#endif
4,167✔
936
}
4,167✔
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
{
12,531✔
943
    // Propagate changes to all other decrypted pages mapping the same memory
6,021✔
944

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

6,021✔
952
    // propagate changes to first page (update may be partial, may also be to last page)
6,021✔
953
    if (first_accessed_local_page < pages_size) {
12,531✔
954
        REALM_ASSERT_EX(is(m_page_state[first_accessed_local_page], UpToDate),
12,531✔
955
                        m_page_state[first_accessed_local_page]);
12,531✔
956
        if (first_accessed_local_page == last_accessed_local_page) {
12,531✔
957
            size_t last_offset = last_accessed_address - page_addr(first_accessed_local_page);
11,652✔
958
            write_and_update_all(first_accessed_local_page, first_offset, last_offset + 1);
11,652✔
959
        }
11,652✔
960
        else
879✔
961
            write_and_update_all(first_accessed_local_page, first_offset, static_cast<size_t>(1) << m_page_shift);
879✔
962
    }
12,531✔
963
    // propagate changes to pages between first and last page (update only full pages)
6,021✔
964
    for (size_t idx = first_accessed_local_page + 1; idx < last_accessed_local_page && idx < pages_size; ++idx) {
73,755✔
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,021✔
969
    if (first_accessed_local_page < last_accessed_local_page && last_accessed_local_page < pages_size) {
12,531✔
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
}
12,531✔
975

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

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

673,401✔
997
    if (header_to_size) {
1,264,521✔
998
        // We know it's an array, and array headers are 8-byte aligned, so it is
665,862✔
999
        // included in the first page which was handled above.
665,862✔
1000
        size = header_to_size(static_cast<const char*>(addr));
1,248,909✔
1001
        required = get_offset_of_address(addr) + size;
1,248,909✔
1002
    }
1,248,909✔
1003

673,401✔
1004
    size_t last_idx = get_local_index_of_address(addr, size == 0 ? 0 : size - 1);
1,264,521✔
1005
    size_t pages_size = m_page_state.size();
1,264,521✔
1006

673,401✔
1007
    // We already checked first_accessed_local_page above, so we start the loop
673,401✔
1008
    // at first_accessed_local_page + 1 to check the following page.
673,401✔
1009
    for (size_t idx = first_accessed_local_page + 1; idx <= last_idx && idx < pages_size; ++idx) {
1,537,053✔
1010
        required -= page_size;
272,532✔
1011
        // force the page reclaimer to look into pages in this chunk
235,248✔
1012
        chunk_ndx = idx >> page_to_chunk_shift;
272,532✔
1013
        if (m_chunk_dont_scan[chunk_ndx])
272,532✔
1014
            m_chunk_dont_scan[chunk_ndx] = 0;
×
1015

235,248✔
1016
        PageState& ps = m_page_state[idx];
272,532✔
1017
        if (is_not(ps, Touched))
272,532✔
1018
            set(ps, Touched);
124,161✔
1019
        if (is_not(ps, UpToDate))
272,532✔
1020
            refresh_page(idx, to_modify ? 0 : required);
124,161✔
1021
        if (to_modify)
272,532✔
1022
            set(ps, Writable);
62,103✔
1023
    }
272,532✔
1024
}
1,264,521✔
1025

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

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

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

1,485✔
1046
    flush();
3,333✔
1047
    m_addr = new_addr;
3,333✔
1048

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

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

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

1060
File::SizeType encrypted_size_to_data_size(File::SizeType size) noexcept
1061
{
4,164✔
1062
    if (size == 0)
4,164✔
1063
        return 0;
111✔
1064
    return fake_offset(size);
4,053✔
1065
}
4,053✔
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