1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "EncryptInplace.h"
18 
19 #include <ext4_utils/ext4.h>
20 #include <ext4_utils/ext4_utils.h>
21 #include <f2fs_sparseblock.h>
22 #include <fcntl.h>
23 #include <time.h>
24 
25 #include <algorithm>
26 #include <vector>
27 
28 #include <android-base/logging.h>
29 #include <android-base/properties.h>
30 #include <android-base/unique_fd.h>
31 
32 enum EncryptInPlaceError {
33     kSuccess,
34     kFailed,
35     kFilesystemNotFound,
36 };
37 
round_up(uint64_t val,size_t amount)38 static uint64_t round_up(uint64_t val, size_t amount) {
39     if (val % amount) val += amount - (val % amount);
40     return val;
41 }
42 
43 class InPlaceEncrypter {
44   public:
45     bool EncryptInPlace(const std::string& crypto_blkdev, const std::string& real_blkdev,
46                         uint64_t nr_sec, bool set_progress_properties);
47     bool ProcessUsedBlock(uint64_t block_num);
48 
49   private:
50     // aligned 32K writes tends to make flash happy.
51     // SD card association recommends it.
52     static const size_t kIOBufferSize = 32768;
53 
54     // Avoid spamming the logs.  Print the "Encrypting blocks" log message once
55     // every 10000 blocks (which is usually every 40 MB or so), and once at the end.
56     static const int kLogInterval = 10000;
57 
58     std::string DescribeFilesystem();
59     void InitFs(const std::string& fs_type, uint64_t blocks_to_encrypt, uint64_t total_blocks,
60                 unsigned int block_size);
61     void UpdateProgress(size_t blocks, bool done);
62     bool EncryptPendingData();
63     bool DoEncryptInPlace();
64 
65     // ext4 methods
66     bool ReadExt4BlockBitmap(uint32_t group, uint8_t* buf);
67     uint64_t FirstBlockInGroup(uint32_t group);
68     uint32_t NumBlocksInGroup(uint32_t group);
69     uint32_t NumBaseMetaBlocksInGroup(uint64_t group);
70     EncryptInPlaceError EncryptInPlaceExt4();
71 
72     // f2fs methods
73     EncryptInPlaceError EncryptInPlaceF2fs();
74 
75     std::string real_blkdev_;
76     std::string crypto_blkdev_;
77     uint64_t nr_sec_;
78     bool set_progress_properties_;
79 
80     android::base::unique_fd realfd_;
81     android::base::unique_fd cryptofd_;
82 
83     time_t time_started_;
84     int remaining_time_;
85 
86     std::string fs_type_;
87     uint64_t blocks_done_;
88     uint64_t blocks_to_encrypt_;
89     unsigned int block_size_;
90     unsigned int cur_pct_;
91 
92     std::vector<uint8_t> io_buffer_;
93     uint64_t first_pending_block_;
94     size_t blocks_pending_;
95 };
96 
DescribeFilesystem()97 std::string InPlaceEncrypter::DescribeFilesystem() {
98     if (fs_type_.empty())
99         return "full block device " + real_blkdev_;
100     else
101         return fs_type_ + " filesystem on " + real_blkdev_;
102 }
103 
104 // Finishes initializing the encrypter, now that the filesystem details are known.
InitFs(const std::string & fs_type,uint64_t blocks_to_encrypt,uint64_t total_blocks,unsigned int block_size)105 void InPlaceEncrypter::InitFs(const std::string& fs_type, uint64_t blocks_to_encrypt,
106                               uint64_t total_blocks, unsigned int block_size) {
107     fs_type_ = fs_type;
108     blocks_done_ = 0;
109     blocks_to_encrypt_ = blocks_to_encrypt;
110     block_size_ = block_size;
111     cur_pct_ = 0;
112 
113     // Allocate the I/O buffer.  kIOBufferSize should always be a multiple of
114     // the filesystem block size, but round it up just in case.
115     io_buffer_.resize(round_up(kIOBufferSize, block_size));
116     first_pending_block_ = 0;
117     blocks_pending_ = 0;
118 
119     LOG(INFO) << "Encrypting " << DescribeFilesystem() << " in-place via " << crypto_blkdev_;
120     LOG(INFO) << blocks_to_encrypt << " blocks (" << (blocks_to_encrypt * block_size) / 1000000
121               << " MB) of " << total_blocks << " blocks are in-use";
122 }
123 
UpdateProgress(size_t blocks,bool done)124 void InPlaceEncrypter::UpdateProgress(size_t blocks, bool done) {
125     // A log message already got printed for blocks_done_ if one was due, so the
126     // next message will be due at the *next* block rounded up to kLogInterval.
127     uint64_t blocks_next_msg = round_up(blocks_done_ + 1, kLogInterval);
128 
129     blocks_done_ += blocks;
130 
131     // Ensure that a log message gets printed at the end, but not if one was
132     // already printed due to the block count being a multiple of kLogInterval.
133     // E.g. we want to show "50000 of 50327" and then "50327 of "50327", but not
134     // "50000 of 50000" and then redundantly "50000 of 50000" again.
135     if (done && blocks_done_ % kLogInterval != 0) blocks_next_msg = blocks_done_;
136 
137     if (blocks_done_ >= blocks_next_msg)
138         LOG(DEBUG) << "Encrypted " << blocks_next_msg << " of " << blocks_to_encrypt_ << " blocks";
139 
140     if (!set_progress_properties_) return;
141 
142     uint64_t new_pct;
143     if (done) {
144         new_pct = 100;
145     } else {
146         new_pct = (blocks_done_ * 100) / std::max<uint64_t>(blocks_to_encrypt_, 1);
147         new_pct = std::min<uint64_t>(new_pct, 99);
148     }
149     if (new_pct > cur_pct_) {
150         cur_pct_ = new_pct;
151         android::base::SetProperty("vold.encrypt_progress", std::to_string(new_pct));
152     }
153 
154     if (cur_pct_ >= 5) {
155         struct timespec time_now;
156         if (clock_gettime(CLOCK_MONOTONIC, &time_now)) {
157             PLOG(WARNING) << "Error getting time while updating encryption progress";
158         } else {
159             double elapsed_time = difftime(time_now.tv_sec, time_started_);
160 
161             uint64_t remaining_blocks = 0;
162             if (blocks_done_ < blocks_to_encrypt_)
163                 remaining_blocks = blocks_to_encrypt_ - blocks_done_;
164 
165             int remaining_time = 0;
166             if (blocks_done_ != 0)
167                 remaining_time = (int)(elapsed_time * remaining_blocks / blocks_done_);
168 
169             // Change time only if not yet set, lower, or a lot higher for
170             // best user experience
171             if (remaining_time_ == -1 || remaining_time < remaining_time_ ||
172                 remaining_time > remaining_time_ + 60) {
173                 remaining_time_ = remaining_time;
174                 android::base::SetProperty("vold.encrypt_time_remaining",
175                                            std::to_string(remaining_time));
176             }
177         }
178     }
179 }
180 
EncryptPendingData()181 bool InPlaceEncrypter::EncryptPendingData() {
182     if (blocks_pending_ == 0) return true;
183 
184     ssize_t bytes = blocks_pending_ * block_size_;
185     uint64_t offset = first_pending_block_ * block_size_;
186 
187     if (pread64(realfd_, &io_buffer_[0], bytes, offset) != bytes) {
188         PLOG(ERROR) << "Error reading real_blkdev " << real_blkdev_ << " for inplace encrypt";
189         return false;
190     }
191 
192     if (pwrite64(cryptofd_, &io_buffer_[0], bytes, offset) != bytes) {
193         PLOG(ERROR) << "Error writing crypto_blkdev " << crypto_blkdev_ << " for inplace encrypt";
194         return false;
195     }
196 
197     UpdateProgress(blocks_pending_, false);
198 
199     blocks_pending_ = 0;
200     return true;
201 }
202 
ProcessUsedBlock(uint64_t block_num)203 bool InPlaceEncrypter::ProcessUsedBlock(uint64_t block_num) {
204     // Flush if the amount of pending data has reached the I/O buffer size, if
205     // there's a gap between the pending blocks and the next block (due to
206     // block(s) not being used by the filesystem and thus not needing
207     // encryption), or if the next block will be aligned to the I/O buffer size.
208     if (blocks_pending_ * block_size_ == io_buffer_.size() ||
209         block_num != first_pending_block_ + blocks_pending_ ||
210         (block_num * block_size_) % io_buffer_.size() == 0) {
211         if (!EncryptPendingData()) return false;
212         first_pending_block_ = block_num;
213     }
214     blocks_pending_++;
215     return true;
216 }
217 
218 // Reads the block bitmap for block group |group| into |buf|.
ReadExt4BlockBitmap(uint32_t group,uint8_t * buf)219 bool InPlaceEncrypter::ReadExt4BlockBitmap(uint32_t group, uint8_t* buf) {
220     uint64_t offset = (uint64_t)aux_info.bg_desc[group].bg_block_bitmap * info.block_size;
221     if (pread64(realfd_, buf, info.block_size, offset) != (ssize_t)info.block_size) {
222         PLOG(ERROR) << "Failed to read block bitmap for block group " << group;
223         return false;
224     }
225     return true;
226 }
227 
FirstBlockInGroup(uint32_t group)228 uint64_t InPlaceEncrypter::FirstBlockInGroup(uint32_t group) {
229     return aux_info.first_data_block + (group * (uint64_t)info.blocks_per_group);
230 }
231 
NumBlocksInGroup(uint32_t group)232 uint32_t InPlaceEncrypter::NumBlocksInGroup(uint32_t group) {
233     uint64_t remaining = aux_info.len_blocks - FirstBlockInGroup(group);
234     return std::min<uint64_t>(info.blocks_per_group, remaining);
235 }
236 
237 // In block groups with an uninitialized block bitmap, we only need to encrypt
238 // the backup superblock and the block group descriptors (if they are present).
NumBaseMetaBlocksInGroup(uint64_t group)239 uint32_t InPlaceEncrypter::NumBaseMetaBlocksInGroup(uint64_t group) {
240     if (!ext4_bg_has_super_block(group)) return 0;
241     return 1 + aux_info.bg_desc_blocks;
242 }
243 
EncryptInPlaceExt4()244 EncryptInPlaceError InPlaceEncrypter::EncryptInPlaceExt4() {
245     if (setjmp(setjmp_env))  // NOLINT
246         return kFilesystemNotFound;
247 
248     if (read_ext(realfd_, 0) != 0) return kFilesystemNotFound;
249 
250     LOG(DEBUG) << "ext4 filesystem has " << aux_info.groups << " block groups";
251 
252     uint64_t blocks_to_encrypt = 0;
253     for (uint32_t group = 0; group < aux_info.groups; group++) {
254         if (aux_info.bg_desc[group].bg_flags & EXT4_BG_BLOCK_UNINIT)
255             blocks_to_encrypt += NumBaseMetaBlocksInGroup(group);
256         else
257             blocks_to_encrypt +=
258                     (NumBlocksInGroup(group) - aux_info.bg_desc[group].bg_free_blocks_count);
259     }
260 
261     InitFs("ext4", blocks_to_encrypt, aux_info.len_blocks, info.block_size);
262 
263     // Encrypt each block group.
264     std::vector<uint8_t> block_bitmap(info.block_size);
265     for (uint32_t group = 0; group < aux_info.groups; group++) {
266         if (!ReadExt4BlockBitmap(group, &block_bitmap[0])) return kFailed;
267 
268         uint64_t first_block_num = FirstBlockInGroup(group);
269         bool uninit = (aux_info.bg_desc[group].bg_flags & EXT4_BG_BLOCK_UNINIT);
270         uint32_t block_count = uninit ? NumBaseMetaBlocksInGroup(group) : NumBlocksInGroup(group);
271 
272         // Encrypt each used block in the block group.
273         for (uint32_t i = 0; i < block_count; i++) {
274             if (uninit || bitmap_get_bit(&block_bitmap[0], i))
275                 ProcessUsedBlock(first_block_num + i);
276         }
277     }
278     return kSuccess;
279 }
280 
encrypt_f2fs_block(uint64_t block_num,void * _encrypter)281 static int encrypt_f2fs_block(uint64_t block_num, void* _encrypter) {
282     InPlaceEncrypter* encrypter = reinterpret_cast<InPlaceEncrypter*>(_encrypter);
283     if (!encrypter->ProcessUsedBlock(block_num)) return -1;
284     return 0;
285 }
286 
EncryptInPlaceF2fs()287 EncryptInPlaceError InPlaceEncrypter::EncryptInPlaceF2fs() {
288     std::unique_ptr<struct f2fs_info, void (*)(struct f2fs_info*)> fs_info(
289             generate_f2fs_info(realfd_), free_f2fs_info);
290     if (!fs_info) return kFilesystemNotFound;
291 
292     InitFs("f2fs", get_num_blocks_used(fs_info.get()), fs_info->total_blocks, fs_info->block_size);
293     if (run_on_used_blocks(0, fs_info.get(), encrypt_f2fs_block, this) != 0) return kFailed;
294     return kSuccess;
295 }
296 
DoEncryptInPlace()297 bool InPlaceEncrypter::DoEncryptInPlace() {
298     EncryptInPlaceError rc;
299 
300     rc = EncryptInPlaceExt4();
301     if (rc != kFilesystemNotFound) return rc == kSuccess;
302 
303     rc = EncryptInPlaceF2fs();
304     if (rc != kFilesystemNotFound) return rc == kSuccess;
305 
306     LOG(WARNING) << "No recognized filesystem found on " << real_blkdev_
307                  << ".  Falling back to encrypting the full block device.";
308     InitFs("", nr_sec_, nr_sec_, 512);
309     for (uint64_t i = 0; i < nr_sec_; i++) {
310         if (!ProcessUsedBlock(i)) return false;
311     }
312     return true;
313 }
314 
EncryptInPlace(const std::string & crypto_blkdev,const std::string & real_blkdev,uint64_t nr_sec,bool set_progress_properties)315 bool InPlaceEncrypter::EncryptInPlace(const std::string& crypto_blkdev,
316                                       const std::string& real_blkdev, uint64_t nr_sec,
317                                       bool set_progress_properties) {
318     struct timespec time_started = {0};
319 
320     real_blkdev_ = real_blkdev;
321     crypto_blkdev_ = crypto_blkdev;
322     nr_sec_ = nr_sec;
323     set_progress_properties_ = set_progress_properties;
324 
325     realfd_.reset(open64(real_blkdev.c_str(), O_RDONLY | O_CLOEXEC));
326     if (realfd_ < 0) {
327         PLOG(ERROR) << "Error opening real_blkdev " << real_blkdev << " for inplace encrypt";
328         return false;
329     }
330 
331     cryptofd_.reset(open64(crypto_blkdev.c_str(), O_WRONLY | O_CLOEXEC));
332     if (cryptofd_ < 0) {
333         PLOG(ERROR) << "Error opening crypto_blkdev " << crypto_blkdev << " for inplace encrypt";
334         return false;
335     }
336 
337     if (clock_gettime(CLOCK_MONOTONIC, &time_started)) {
338         PLOG(WARNING) << "Error getting time at start of in-place encryption";
339         // Note - continue anyway - we'll run with 0
340     }
341     time_started_ = time_started.tv_sec;
342     remaining_time_ = -1;
343 
344     bool success = DoEncryptInPlace();
345 
346     if (success) success &= EncryptPendingData();
347 
348     if (success && fsync(cryptofd_) != 0) {
349         PLOG(ERROR) << "Error syncing " << crypto_blkdev_;
350         success = false;
351     }
352 
353     if (!success) {
354         LOG(ERROR) << "In-place encryption of " << DescribeFilesystem() << " failed";
355         return false;
356     }
357     if (blocks_done_ != blocks_to_encrypt_) {
358         LOG(WARNING) << "blocks_to_encrypt (" << blocks_to_encrypt_
359                      << ") was incorrect; we actually encrypted " << blocks_done_
360                      << " blocks.  Encryption progress was inaccurate";
361     }
362     // Make sure vold.encrypt_progress gets set to 100.
363     UpdateProgress(0, true);
364     LOG(INFO) << "Successfully encrypted " << DescribeFilesystem();
365     return true;
366 }
367 
368 // Encrypts |real_blkdev| in-place by reading the data from |real_blkdev| and
369 // writing it to |crypto_blkdev|, which should be a dm-crypt or dm-default-key
370 // device backed by |real_blkdev|.  The size to encrypt is |nr_sec| 512-byte
371 // sectors; however, if a filesystem is detected, then its size will be used
372 // instead, and only the in-use blocks of the filesystem will be encrypted.
encrypt_inplace(const std::string & crypto_blkdev,const std::string & real_blkdev,uint64_t nr_sec,bool set_progress_properties)373 bool encrypt_inplace(const std::string& crypto_blkdev, const std::string& real_blkdev,
374                      uint64_t nr_sec, bool set_progress_properties) {
375     LOG(DEBUG) << "encrypt_inplace(" << crypto_blkdev << ", " << real_blkdev << ", " << nr_sec
376                << ", " << (set_progress_properties ? "true" : "false") << ")";
377 
378     InPlaceEncrypter encrypter;
379     return encrypter.EncryptInPlace(crypto_blkdev, real_blkdev, nr_sec, set_progress_properties);
380 }
381