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

realm / realm-core / 1729

03 Oct 2023 04:48PM UTC coverage: 91.615% (-0.05%) from 91.661%
1729

push

Evergreen

web-flow
fix comment to better reflect async open logic (#7025)

94270 of 173442 branches covered (0.0%)

5 of 5 new or added lines in 1 file covered. (100.0%)

165 existing lines in 13 files now uncovered.

230409 of 251498 relevant lines covered (91.61%)

6767420.9 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,953✔
60
}
1,953✔
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
    {
19,827✔
91
        return iv1 == other.iv1 && iv2 == other.iv2 && hmac1 == other.hmac1 && hmac2 == other.hmac2;
19,827✔
92
    }
19,827✔
93
    bool operator!=(const iv_table& other) const
94
    {
12,270✔
95
        return !(*this == other);
12,270✔
96
    }
12,270✔
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
{
604,737✔
110
    REALM_ASSERT(pos >= 0);
604,737✔
111
    const size_t index = static_cast<size_t>(pos) / block_size;
604,737✔
112
    const size_t metadata_page_count = index / blocks_per_metadata_block + 1;
604,737✔
113
    return Int(pos + metadata_page_count * block_size);
604,737✔
114
}
604,737✔
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
{
3,948✔
120
    REALM_ASSERT(pos >= 0);
3,948✔
121
    const size_t index = static_cast<size_t>(pos) / block_size;
3,948✔
122
    const size_t metadata_page_count = (index + blocks_per_metadata_block) / (blocks_per_metadata_block + 1);
3,948✔
123
    return pos - metadata_page_count * block_size;
3,948✔
124
}
3,948✔
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
{
114,855✔
129
    REALM_ASSERT(pos >= 0);
114,855✔
130
    const size_t index = static_cast<size_t>(pos) / block_size;
114,855✔
131
    const size_t metadata_block = index / blocks_per_metadata_block;
114,855✔
132
    const size_t metadata_index = index & (blocks_per_metadata_block - 1);
114,855✔
133
    return off_t(metadata_block * (blocks_per_metadata_block + 1) * block_size + metadata_index * metadata_size);
114,855✔
134
}
114,855✔
135

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

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

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

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

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

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

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

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

2,928✔
245
    return m_iv_buffer[idx];
6,201✔
246
}
6,201✔
247

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

207,804✔
253
    // Constant-time memcmp to avoid timing attacks
207,804✔
254
    uint8_t result = 0;
432,540✔
255
    for (size_t i = 0; i < 224 / 8; ++i)
12,543,660✔
256
        result |= buffer[i] ^ hmac[i];
12,111,120✔
257
    return result == 0;
432,540✔
258
}
432,540✔
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
{
948✔
263
    REALM_ASSERT_EX(page_ndx_in_file_expected < end_page_ndx_in_file, page_ndx_in_file_expected,
948✔
264
                    end_page_ndx_in_file);
948✔
265
    // the indices returned are page indices, not block indices
480✔
266
    util::FlatMap<size_t, IVRefreshState> page_states;
948✔
267

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

480✔
278
    get_iv_table(fd, data_pos, IVLookupMode::Refetch);
948✔
279

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

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

258,246✔
375
        if (actual == 0)
495,924✔
376
            return bytes_read;
834✔
377

257,673✔
378
        iv_table& iv = get_iv_table(fd, pos, retry_count == 0 ? IVLookupMode::UseCache : IVLookupMode::Refetch);
495,090✔
379
        if (iv.iv1 == 0) {
495,090✔
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,765✔
390
        if (!check_hmac(m_rw_buffer.get(), actual, iv.hmac1)) {
432,462✔
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,729✔
427
        // We may expect some adress ranges of the destination buffer of
207,729✔
428
        // AESCryptor::read() to stay unmodified, i.e. being overwritten with
207,729✔
429
        // the same bytes as already present, and may have read-access to these
207,729✔
430
        // from other threads while decryption is taking place.
207,729✔
431
        //
207,729✔
432
        // However, some implementations of AES_cbc_encrypt(), in particular
207,729✔
433
        // OpenSSL, will put garbled bytes as an intermediate step during the
207,729✔
434
        // operation which will lead to incorrect data being read by other
207,729✔
435
        // readers concurrently accessing that page. Incorrect data leads to
207,729✔
436
        // crashes.
207,729✔
437
        //
207,729✔
438
        // We therefore decrypt to a temporary buffer first and then copy the
207,729✔
439
        // completely decrypted data after.
207,729✔
440
        crypt(mode_Decrypt, pos, m_dst_buffer.get(), m_rw_buffer.get(), reinterpret_cast<const char*>(&iv.iv1));
432,390✔
441
        memcpy(dst, m_dst_buffer.get(), block_size);
432,390✔
442

207,729✔
443
        pos += block_size;
432,390✔
444
        dst += block_size;
432,390✔
445
        bytes_read += block_size;
432,390✔
446
        retry_count = 0;
432,390✔
447
    }
432,390✔
448
    return bytes_read;
314,415✔
449
}
327,384✔
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,009✔
486
    REALM_ASSERT(size % block_size == 0);
66,009✔
487
    while (size > 0) {
174,309✔
488
        iv_table& iv = get_iv_table(fd, pos);
108,300✔
489

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

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

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

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

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

259,230✔
523
#if REALM_PLATFORM_APPLE
281,460✔
524
    CCCryptorRef cryptor = mode == mode_Encrypt ? m_encr : m_decr;
224,661✔
525
    CCCryptorReset(cryptor, iv);
281,460✔
526

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

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

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

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

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

600
char* EncryptedFileMapping::page_addr(size_t local_page_ndx) const noexcept
601
{
438,981✔
602
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
438,981✔
603
    return static_cast<char*>(m_addr) + (local_page_ndx << m_page_shift);
438,981✔
604
}
438,981✔
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,327✔
621
    REALM_ASSERT_EX(local_page_ndx < m_page_state.size(), local_page_ndx, m_page_state.size());
129,327✔
622
    // Precondition: this method must never be called for a page which
101,874✔
623
    // is already up to date.
101,874✔
624
    REALM_ASSERT(is_not(m_page_state[local_page_ndx], UpToDate));
129,327✔
625
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
382,662✔
626
        EncryptedFileMapping* m = m_file.mappings[i];
254,049✔
627
        size_t page_ndx_in_file = local_page_ndx + m_first_page;
254,049✔
628
        if (m == this || !m->contains_page(page_ndx_in_file))
254,049✔
629
            continue;
253,179✔
630

450✔
631
        size_t shadow_mapping_local_ndx = page_ndx_in_file - m->m_first_page;
870✔
632
        if (is(m->m_page_state[shadow_mapping_local_ndx], UpToDate)) {
870✔
633
            memcpy(page_addr(local_page_ndx), m->page_addr(shadow_mapping_local_ndx),
714✔
634
                   static_cast<size_t>(1ULL << m_page_shift));
714✔
635
            return true;
714✔
636
        }
714✔
637
    }
870✔
638
    return false;
128,982✔
639
}
129,327✔
640

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

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

696
void EncryptedFileMapping::mark_pages_for_IV_check()
697
{
1,311✔
698
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
2,736✔
699
        EncryptedFileMapping* m = m_file.mappings[i];
1,425✔
700
        for (size_t pg = m->get_start_index(); pg < m->get_end_index(); ++pg) {
506,751✔
701
            size_t local_page_ndx = pg - m->m_first_page;
505,326✔
702
            if (is(m->m_page_state[local_page_ndx], UpToDate) &&
505,326✔
703
                is_not(m->m_page_state[local_page_ndx], Dirty | Writable)) {
404,427✔
704
                REALM_ASSERT(is_not(m->m_page_state[local_page_ndx], StaleIV));
1,128✔
705
                clear(m->m_page_state[local_page_ndx], UpToDate);
1,128✔
706
                set(m->m_page_state[local_page_ndx], StaleIV);
1,128✔
707
            }
1,128✔
708
        }
505,326✔
709
    }
1,425✔
710
}
1,311✔
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,394✔
715
    REALM_ASSERT(is(m_page_state[local_page_ndx], Writable));
74,394✔
716
    REALM_ASSERT(is(m_page_state[local_page_ndx], UpToDate));
74,394✔
717
    // Go through all other mappings of this file and copy changes into those mappings
55,599✔
718
    size_t page_ndx_in_file = local_page_ndx + m_first_page;
74,394✔
719
    for (size_t i = 0; i < m_file.mappings.size(); ++i) {
222,849✔
720
        EncryptedFileMapping* m = m_file.mappings[i];
148,455✔
721
        if (m != this && m->contains_page(page_ndx_in_file)) {
148,455✔
722
            size_t shadow_local_page_ndx = page_ndx_in_file - m->m_first_page;
9,693✔
723
            if (is(m->m_page_state[shadow_local_page_ndx], UpToDate) ||
9,693✔
724
                is(m->m_page_state[shadow_local_page_ndx], StaleIV)) { // only keep up to data pages up to date
9,687✔
725
                memcpy(m->page_addr(shadow_local_page_ndx) + begin_offset, page_addr(local_page_ndx) + begin_offset,
9,663✔
726
                       end_offset - begin_offset);
9,663✔
727
                if (is(m->m_page_state[shadow_local_page_ndx], StaleIV)) {
9,663✔
728
                    set(m->m_page_state[shadow_local_page_ndx], UpToDate);
102✔
729
                    clear(m->m_page_state[shadow_local_page_ndx], StaleIV);
102✔
730
                }
102✔
731
            }
9,663✔
732
            else {
30✔
733
                m->mark_outdated(shadow_local_page_ndx);
30✔
734
            }
30✔
735
        }
9,693✔
736
    }
148,455✔
737
    set(m_page_state[local_page_ndx], Dirty);
74,394✔
738
    clear(m_page_state[local_page_ndx], Writable);
74,394✔
739
    clear(m_page_state[local_page_ndx], StaleIV);
74,394✔
740
    size_t chunk_ndx = local_page_ndx >> page_to_chunk_shift;
74,394✔
741
    if (m_chunk_dont_scan[chunk_ndx])
74,394✔
742
        m_chunk_dont_scan[chunk_ndx] = 0;
×
743
}
74,394✔
744

745

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

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

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

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

779
void EncryptedFileMapping::validate() noexcept
780
{
8,085✔
781
#ifdef REALM_DEBUG
8,085✔
782
    const size_t num_local_pages = m_page_state.size();
8,085✔
783
    for (size_t local_page_ndx = 0; local_page_ndx < num_local_pages; ++local_page_ndx)
5,446,482✔
784
        validate_page(local_page_ndx);
5,438,397✔
785
#endif
8,085✔
786
}
8,085✔
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,085✔
899
    const size_t num_dirty_pages = m_page_state.size();
8,085✔
900
    for (size_t local_page_ndx = 0; local_page_ndx < num_dirty_pages; ++local_page_ndx) {
5,446,482✔
901
        if (is_not(m_page_state[local_page_ndx], Dirty)) {
5,438,397✔
902
            validate_page(local_page_ndx);
5,373,240✔
903
            continue;
5,373,240✔
904
        }
5,373,240✔
905

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

3,789✔
912
    validate();
8,085✔
913
}
8,085✔
914

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

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

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

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

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

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

642,186✔
1004
    size_t last_idx = get_local_index_of_address(addr, size == 0 ? 0 : size - 1);
1,240,950✔
1005
    size_t pages_size = m_page_state.size();
1,240,950✔
1006

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

228,288✔
1016
        PageState& ps = m_page_state[idx];
265,644✔
1017
        if (is_not(ps, Touched))
265,644✔
1018
            set(ps, Touched);
124,167✔
1019
        if (is_not(ps, UpToDate))
265,644✔
1020
            refresh_page(idx, to_modify ? 0 : required);
124,167✔
1021
        if (to_modify)
265,644✔
1022
            set(ps, Writable);
62,103✔
1023
    }
265,644✔
1024
}
1,240,950✔
1025

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

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

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

1,479✔
1046
    flush();
3,228✔
1047
    m_addr = new_addr;
3,228✔
1048

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

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

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

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