aboutsummaryrefslogtreecommitdiff
path: root/core/nvram_manager.cpp
blob: c305ceca009bddf6f03297b0ee25869fa3ef208d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nvram/core/nvram_manager.h"

extern "C" {
#include <inttypes.h>
#include <string.h>
}  // extern "C"

#include <nvram/core/logger.h>

#include "crypto.h"

using namespace nvram::storage;

namespace nvram {

namespace {

// Maximum size of a single space's contents.
constexpr size_t kMaxSpaceSize = 1024;

// Maximum authorization blob size;
constexpr size_t kMaxAuthSize = 32;

// The bitmask of all supported control flags.
constexpr uint32_t kSupportedControlsMask =
    (1 << NV_CONTROL_PERSISTENT_WRITE_LOCK) |
    (1 << NV_CONTROL_BOOT_WRITE_LOCK) |
    (1 << NV_CONTROL_BOOT_READ_LOCK) |
    (1 << NV_CONTROL_WRITE_AUTHORIZATION) |
    (1 << NV_CONTROL_READ_AUTHORIZATION) |
    (1 << NV_CONTROL_WRITE_EXTEND);

// Convert the |space.controls| bitmask to vector representation.
nvram_result_t GetControlsVector(const NvramSpace& space,
                                 Vector<nvram_control_t>* controls) {
  for (size_t control = 0; control < sizeof(uint32_t) * 8; ++control) {
    if (space.HasControl(control)) {
      if (!controls->Resize(controls->size() + 1)) {
        NVRAM_LOG_ERR("Allocation failure.");
        return NV_RESULT_INTERNAL_ERROR;
      }
      (*controls)[controls->size() - 1] = static_cast<nvram_control_t>(control);
    }
  }
  return NV_RESULT_SUCCESS;
}

// Constant time memory block comparison.
bool ConstantTimeEquals(const Blob& a, const Blob& b) {
  if (a.size() != b.size())
    return false;

  // The volatile qualifiers prevent the compiler from making assumptions that
  // allow shortcuts:
  //  * The entire array data must be read from memory.
  //  * Marking |result| volatile ensures the subsequent loop iterations must
  //    still store to |result|, thus avoiding the loop to exit early.
  // This achieves the desired constant-time behavior.
  volatile const uint8_t* data_a = a.data();
  volatile const uint8_t* data_b = b.data();
  volatile uint8_t result = 0;
  for (size_t i = 0; i < a.size(); ++i) {
    result |= data_a[i] ^ data_b[i];
  }

  return result == 0;
}

// A standard minimum function.
template <typename Type>
const Type& min(const Type& a, const Type& b) {
  return (a < b) ? a : b;
}

// Filter status codes from the storage layer to only include known values.
// Anything outside the range will be mapped to the generic |kStorageError|.
storage::Status SanitizeStorageStatus(storage::Status status) {
  switch (status) {
    case storage::Status::kSuccess:
      return storage::Status::kSuccess;
    case storage::Status::kNotFound:
      return storage::Status::kNotFound;
    case storage::Status::kStorageError:
      return storage::Status::kStorageError;
  }
  NVRAM_LOG_ERR("Unknown status code %u!", status);
  return storage::Status::kStorageError;
}

}  // namespace

// Looks at |request| to determine the command to execute, then invokes
// the appropriate handler.
void NvramManager::Dispatch(const nvram::Request& request,
                            nvram::Response* response) {
  nvram_result_t result = NV_RESULT_INVALID_PARAMETER;
  const nvram::RequestUnion& input = request.payload;
  nvram::ResponseUnion* output = &response->payload;

  switch (input.which()) {
    case nvram::COMMAND_GET_INFO:
      result = GetInfo(*input.get<COMMAND_GET_INFO>(),
                       &output->Activate<COMMAND_GET_INFO>());
      break;
    case nvram::COMMAND_CREATE_SPACE:
      result = CreateSpace(*input.get<COMMAND_CREATE_SPACE>(),
                           &output->Activate<COMMAND_CREATE_SPACE>());
      break;
    case nvram::COMMAND_GET_SPACE_INFO:
      result = GetSpaceInfo(*input.get<COMMAND_GET_SPACE_INFO>(),
                            &output->Activate<COMMAND_GET_SPACE_INFO>());
      break;
    case nvram::COMMAND_DELETE_SPACE:
      result = DeleteSpace(*input.get<COMMAND_DELETE_SPACE>(),
                           &output->Activate<COMMAND_DELETE_SPACE>());
      break;
    case nvram::COMMAND_DISABLE_CREATE:
      result = DisableCreate(*input.get<COMMAND_DISABLE_CREATE>(),
                             &output->Activate<COMMAND_DISABLE_CREATE>());
      break;
    case nvram::COMMAND_WRITE_SPACE:
      result = WriteSpace(*input.get<COMMAND_WRITE_SPACE>(),
                          &output->Activate<COMMAND_WRITE_SPACE>());
      break;
    case nvram::COMMAND_READ_SPACE:
      result = ReadSpace(*input.get<COMMAND_READ_SPACE>(),
                         &output->Activate<COMMAND_READ_SPACE>());
      break;
    case nvram::COMMAND_LOCK_SPACE_WRITE:
      result = LockSpaceWrite(*input.get<COMMAND_LOCK_SPACE_WRITE>(),
                              &output->Activate<COMMAND_LOCK_SPACE_WRITE>());
      break;
    case nvram::COMMAND_LOCK_SPACE_READ:
      result = LockSpaceRead(*input.get<COMMAND_LOCK_SPACE_READ>(),
                             &output->Activate<COMMAND_LOCK_SPACE_READ>());
      break;
    case nvram::COMMAND_WIPE_STORAGE:
      result = WipeStorage(*input.get<COMMAND_WIPE_STORAGE>(),
                           &output->Activate<COMMAND_WIPE_STORAGE>());
      break;
    case nvram::COMMAND_DISABLE_WIPE:
      result = DisableWipe(*input.get<COMMAND_DISABLE_WIPE>(),
                           &output->Activate<COMMAND_DISABLE_WIPE>());
      break;
  }

  response->result = result;
}

nvram_result_t NvramManager::GetInfo(const GetInfoRequest& /* request */,
                                     GetInfoResponse* response) {
  NVRAM_LOG_INFO("GetInfo");

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  // TODO: Get better values for total and available size from the storage
  // layer.
  response->total_size = kMaxSpaceSize * kMaxSpaces;
  response->available_size = kMaxSpaceSize * (kMaxSpaces - num_spaces_);
  response->max_space_size = kMaxSpaceSize;
  response->max_spaces = kMaxSpaces;
  Vector<uint32_t>& space_list = response->space_list;
  if (!space_list.Resize(num_spaces_)) {
    NVRAM_LOG_ERR("Allocation failure.");
    return NV_RESULT_INTERNAL_ERROR;
  }
  for (size_t i = 0; i < num_spaces_; ++i) {
    space_list[i] = spaces_[i].index;
  }
  response->wipe_disabled = disable_wipe_;

  return NV_RESULT_SUCCESS;
}

nvram_result_t NvramManager::CreateSpace(const CreateSpaceRequest& request,
                                         CreateSpaceResponse* /* response */) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("CreateSpace Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  if (disable_create_) {
    NVRAM_LOG_INFO("Creation of further spaces is disabled.");
    return NV_RESULT_OPERATION_DISABLED;
  }

  if (FindSpace(index) != kMaxSpaces) {
    NVRAM_LOG_INFO("Space 0x%" PRIx32 " already exists.", index);
    return NV_RESULT_SPACE_ALREADY_EXISTS;
  }

  if (num_spaces_ + 1 > kMaxSpaces) {
    NVRAM_LOG_INFO("Too many spaces.");
    return NV_RESULT_INVALID_PARAMETER;
  }

  if (request.size > kMaxSpaceSize) {
    NVRAM_LOG_INFO("Create request exceeds max space size.");
    return NV_RESULT_INVALID_PARAMETER;
  }

  if (request.authorization_value.size() > kMaxAuthSize) {
    NVRAM_LOG_INFO("Authorization blob too large.");
    return NV_RESULT_INVALID_PARAMETER;
  }

  uint32_t controls = 0;
  for (uint32_t control : request.controls) {
    controls |= (1 << control);
  }
  if ((controls & ~kSupportedControlsMask) != 0) {
    NVRAM_LOG_INFO("Bad controls.");
    return NV_RESULT_INVALID_PARAMETER;
  }
  if ((controls & (1 << NV_CONTROL_PERSISTENT_WRITE_LOCK)) != 0 &&
      (controls & (1 << NV_CONTROL_BOOT_WRITE_LOCK)) != 0) {
    NVRAM_LOG_INFO("Write lock controls are exclusive.");
    return NV_RESULT_INVALID_PARAMETER;
  }
  if ((controls & (1 << NV_CONTROL_WRITE_EXTEND)) != 0 &&
      request.size != crypto::kSHA256DigestSize) {
    NVRAM_LOG_INFO("Write-extended space size must be %zu.",
                   crypto::kSHA256DigestSize);
    return NV_RESULT_INVALID_PARAMETER;
  }

  // Mark the index as allocated.
  spaces_[num_spaces_].index = index;
  spaces_[num_spaces_].write_locked = false;
  spaces_[num_spaces_].read_locked = false;
  ++num_spaces_;

  // Create a space record.
  NvramSpace space;
  space.flags = 0;
  space.controls = controls;

  // Copy the auth blob.
  if (space.HasControl(NV_CONTROL_WRITE_AUTHORIZATION) ||
      space.HasControl(NV_CONTROL_READ_AUTHORIZATION)) {
    if (!space.authorization_value.Assign(request.authorization_value.data(),
                                          request.authorization_value.size())) {
      NVRAM_LOG_ERR("Allocation failure.");
      return NV_RESULT_INTERNAL_ERROR;
    }
  }

  // Initialize the space content.
  if (!space.contents.Resize(request.size)) {
    NVRAM_LOG_ERR("Allocation failure.");
    return NV_RESULT_INTERNAL_ERROR;
  }
  memset(space.contents.data(), 0, request.size);

  // Write the header before the space data. This ensures that all space
  // definitions present in storage are also recorded in the header. Thus, the
  // set of spaces present in the header is always a superset of the set of
  // spaces that have state in storage. If there's a crash after writing the
  // header but before writing the space information, the space data will be
  // missing in storage. The initialization code handles this by checking the
  // for the space data corresponding to the index marked as provisional in the
  // header.
  nvram_result_t result;
  if ((result = WriteHeader(Optional<uint32_t>(index))) != NV_RESULT_SUCCESS ||
      (result = WriteSpace(index, space)) != NV_RESULT_SUCCESS) {
    --num_spaces_;
  }
  return result;
}

nvram_result_t NvramManager::GetSpaceInfo(const GetSpaceInfoRequest& request,
                                          GetSpaceInfoResponse* response) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("GetSpaceInfo Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  response->size = space_record.persistent.contents.size();

  result = GetControlsVector(space_record.persistent, &response->controls);
  if (result != NV_RESULT_SUCCESS) {
    return NV_RESULT_INTERNAL_ERROR;
  }

  if (space_record.persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) {
    response->read_locked = space_record.transient->read_locked;
  }

  if (space_record.persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) {
    response->write_locked =
        space_record.persistent.HasFlag(NvramSpace::kFlagWriteLocked);
  } else if (space_record.persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) {
    response->write_locked = space_record.transient->write_locked;
  }

  return NV_RESULT_SUCCESS;
}

nvram_result_t NvramManager::DeleteSpace(const DeleteSpaceRequest& request,
                                         DeleteSpaceResponse* /* response */) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("DeleteSpace Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  result = space_record.CheckWriteAccess(request.authorization_value);
  if (result != NV_RESULT_SUCCESS) {
    return result;
  }

  // Delete the space. First mark the space as provisionally removed in the
  // header. Then, delete the space data from storage. This allows orphaned
  // space data be cleaned up after a crash.
  SpaceListEntry tmp = spaces_[space_record.array_index];
  spaces_[space_record.array_index] = spaces_[num_spaces_ - 1];
  --num_spaces_;
  result = WriteHeader(Optional<uint32_t>(index));
  if (result == NV_RESULT_SUCCESS) {
    switch (SanitizeStorageStatus(persistence::DeleteSpace(index))) {
      case storage::Status::kStorageError:
        NVRAM_LOG_ERR("Failed to delete space 0x%" PRIx32 " data.", index);
        result = NV_RESULT_INTERNAL_ERROR;
        break;
      case storage::Status::kNotFound:
        // The space was missing even if it shouldn't have been. Log an error,
        // but return success as we're in the desired state.
        NVRAM_LOG_ERR("Space 0x%" PRIx32 " data missing on deletion.", index);
        return NV_RESULT_SUCCESS;
      case storage::Status::kSuccess:
        return NV_RESULT_SUCCESS;
    }
  }

  // Failed to delete, re-add the transient state to |spaces_|.
  spaces_[num_spaces_] = tmp;
  ++num_spaces_;
  return result;
}

nvram_result_t NvramManager::DisableCreate(
    const DisableCreateRequest& /* request */,
    DisableCreateResponse* /* response */) {
  NVRAM_LOG_INFO("DisableCreate");

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  // Set the |disable_create_| flag and call |WriteHeader| to persist the flag
  // such that it remains effective after a reboot. Make sure to restore the
  // current value of |disable_create_| if the write call fails, as we return an
  // error in that case and client code would not expect state changes.
  bool disable_create_previous = disable_create_;
  disable_create_ = true;
  nvram_result_t result = WriteHeader(Optional<uint32_t>());
  if (result != NV_RESULT_SUCCESS) {
    disable_create_ = disable_create_previous;
  }
  return result;
}

nvram_result_t NvramManager::WriteSpace(const WriteSpaceRequest& request,
                                        WriteSpaceResponse* /* response */) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("WriteSpace Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  result = space_record.CheckWriteAccess(request.authorization_value);
  if (result != NV_RESULT_SUCCESS) {
    return result;
  }

  Blob& contents = space_record.persistent.contents;
  if (space_record.persistent.HasControl(NV_CONTROL_WRITE_EXTEND)) {
    // Concatenate the current space |contents| with the input data.
    Blob sha256_input;
    if (!sha256_input.Resize(contents.size() + request.buffer.size())) {
      return NV_RESULT_INTERNAL_ERROR;
    }
    memcpy(sha256_input.data(), contents.data(), contents.size());
    memcpy(sha256_input.data() + contents.size(), request.buffer.data(),
           request.buffer.size());

    // Compute the SHA-256 digest and write it back to |contents|.
    crypto::SHA256(sha256_input.data(), sha256_input.size(), contents.data(),
                   contents.size());
  } else {
    if (contents.size() < request.buffer.size()) {
      return NV_RESULT_INVALID_PARAMETER;
    }

    memcpy(contents.data(), request.buffer.data(), request.buffer.size());
    memset(contents.data() + request.buffer.size(), 0x0,
           contents.size() - request.buffer.size());
  }

  return WriteSpace(index, space_record.persistent);
}

nvram_result_t NvramManager::ReadSpace(const ReadSpaceRequest& request,
                                       ReadSpaceResponse* response) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("ReadSpace Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  result = space_record.CheckReadAccess(request.authorization_value);
  if (result != NV_RESULT_SUCCESS) {
    return result;
  }

  if (!response->buffer.Assign(space_record.persistent.contents.data(),
                               space_record.persistent.contents.size())) {
    NVRAM_LOG_ERR("Allocation failure.");
    return NV_RESULT_INTERNAL_ERROR;
  }

  return NV_RESULT_SUCCESS;
}

nvram_result_t NvramManager::LockSpaceWrite(
    const LockSpaceWriteRequest& request,
    LockSpaceWriteResponse* /* response */) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("LockSpaceWrite Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  result = space_record.CheckWriteAccess(request.authorization_value);
  if (result != NV_RESULT_SUCCESS) {
    return result;
  }

  if (space_record.persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) {
    space_record.persistent.SetFlag(NvramSpace::kFlagWriteLocked);
    return WriteSpace(index, space_record.persistent);
  } else if (space_record.persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) {
    space_record.transient->write_locked = true;
    return NV_RESULT_SUCCESS;
  }

  NVRAM_LOG_ERR("Space not configured for write locking.");
  return NV_RESULT_INVALID_PARAMETER;
}

nvram_result_t NvramManager::LockSpaceRead(
    const LockSpaceReadRequest& request,
    LockSpaceReadResponse* /* response */) {
  const uint32_t index = request.index;
  NVRAM_LOG_INFO("LockSpaceRead Ox%" PRIx32, index);

  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

  SpaceRecord space_record;
  nvram_result_t result;
  if (!LoadSpaceRecord(index, &space_record, &result)) {
    return result;
  }

  result = space_record.CheckReadAccess(request.authorization_value);
  if (result != NV_RESULT_SUCCESS) {
    return result;
  }

  if (space_record.persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) {
    space_record.transient->read_locked = true;
    return NV_RESULT_SUCCESS;
  }

  NVRAM_LOG_ERR("Space not configured for read locking.");
  return NV_RESULT_INVALID_PARAMETER;
}

nvram_result_t NvramManager::WipeStorage(
    const WipeStorageRequest& /* request */,
    WipeStorageResponse* /* response */) {
  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

#ifdef NVRAM_WIPE_STORAGE_SUPPORT
  if (disable_wipe_) {
    return NV_RESULT_OPERATION_DISABLED;
  }

  // Go through all spaces and wipe the corresponding data. Note that the header
  // is only updated once all space data is gone. This will "break" all spaces
  // that are left declared but don't have data. This situation can be observed
  // if we crash somewhere during the wiping process before clearing the header.
  //
  // Note that we deliberately choose this wiping sequence so we can never end
  // up in a state where the header appears clean but existing space data
  // remains.
  //
  // As a final note, the ideal solution would be to atomically clear the header
  // and delete all space data. While more desirable from an operational point
  // of view, this would drastically complicate storage layer requirements to
  // support cross-object atomicity instead of per-object atomicity.
  for (size_t i = 0; i < num_spaces_; ++i) {
    const uint32_t index = spaces_[i].index;
    switch (SanitizeStorageStatus(persistence::DeleteSpace(index))) {
      case storage::Status::kStorageError:
        NVRAM_LOG_ERR("Failed to wipe space 0x%" PRIx32 " data.", index);
        return NV_RESULT_INTERNAL_ERROR;
      case storage::Status::kNotFound:
        // The space was missing even if it shouldn't have been. This may occur
        // if a previous wiping attempt was aborted half-way. Log an error, but
        // return success as we're in the desired state.
        NVRAM_LOG_WARN("Space 0x%" PRIx32 " data missing on wipe.", index);
        break;
      case storage::Status::kSuccess:
        break;
    }
  }

  // All spaces are gone, clear the header.
  num_spaces_ = 0;
  return WriteHeader(Optional<uint32_t>());
#else  // NVRAM_WIPE_STORAGE_SUPPORT
  // We're not accessing the flag member, so prevent a compiler warning. The
  // alternative of conditionally including the member in the class declaration
  // looks cleaner at first sight, but comes with the risk of
  // NVRAM_WIPE_STORAGE_SUPPORT polarity mismatches between compilation units,
  // which is more subtly dangerous, so we rather keep the member even for the
  // case in which it is not used.
  (void)disable_wipe_;
  return NV_RESULT_OPERATION_DISABLED;
#endif  // NVRAM_WIPE_STORAGE_SUPPORT
}

nvram_result_t NvramManager::DisableWipe(
    const DisableWipeRequest& /* request */,
    DisableWipeResponse* /* response */) {
  if (!Initialize())
    return NV_RESULT_INTERNAL_ERROR;

#ifdef NVRAM_WIPE_STORAGE_SUPPORT
  disable_wipe_ = true;
  return NV_RESULT_SUCCESS;
#else  // NVRAM_WIPE_STORAGE_SUPPORT
  return NV_RESULT_OPERATION_DISABLED;
#endif  // NVRAM_WIPE_STORAGE_SUPPORT
}

nvram_result_t NvramManager::SpaceRecord::CheckWriteAccess(
    const Blob& authorization_value) {
  if (persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) {
    if (persistent.HasFlag(NvramSpace::kFlagWriteLocked)) {
      NVRAM_LOG_INFO("Attempt to write persistently locked space 0x%" PRIx32
                     ".",
                     transient->index);
      return NV_RESULT_OPERATION_DISABLED;
    }
  } else if (persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) {
    if (transient->write_locked) {
      NVRAM_LOG_INFO("Attempt to write per-boot locked space 0x%" PRIx32 ".",
                     transient->index);
      return NV_RESULT_OPERATION_DISABLED;
    }
  }

  if (persistent.HasControl(NV_CONTROL_WRITE_AUTHORIZATION) &&
      !ConstantTimeEquals(persistent.authorization_value,
                          authorization_value)) {
    NVRAM_LOG_INFO(
        "Authorization value mismatch for write access to space 0x%" PRIx32 ".",
        transient->index);
    return NV_RESULT_ACCESS_DENIED;
  }

  // All checks passed, allow the write.
  return NV_RESULT_SUCCESS;
}

nvram_result_t NvramManager::SpaceRecord::CheckReadAccess(
    const Blob& authorization_value) {
  if (persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) {
    if (transient->read_locked) {
      NVRAM_LOG_INFO("Attempt to read per-boot locked space 0x%" PRIx32 ".",
                     transient->index);
      return NV_RESULT_OPERATION_DISABLED;
    }
  }

  if (persistent.HasControl(NV_CONTROL_READ_AUTHORIZATION) &&
      !ConstantTimeEquals(persistent.authorization_value,
                          authorization_value)) {
    NVRAM_LOG_INFO(
        "Authorization value mismatch for read access to space 0x%" PRIx32 ".",
        transient->index);
    return NV_RESULT_ACCESS_DENIED;
  }

  // All checks passed, allow the read.
  return NV_RESULT_SUCCESS;
}

bool NvramManager::Initialize() {
  if (initialized_)
    return true;

  NvramHeader header;
  switch (SanitizeStorageStatus(persistence::LoadHeader(&header))) {
    case storage::Status::kStorageError:
      NVRAM_LOG_ERR("Init failed to load header.");
      return false;
    case storage::Status::kNotFound:
      // No header in storage. This happens the very first time we initialize
      // on a fresh device where the header isn't present yet. The first write
      // will flush the fresh header to storage.
      initialized_ = true;
      return true;
    case storage::Status::kSuccess:
      if (header.version > NvramHeader::kVersion) {
        NVRAM_LOG_ERR("Storage format %" PRIu32 " is more recent than %" PRIu32
                      ", aborting.",
                      header.version, NvramHeader::kVersion);
        return false;
      }
      break;
  }

  // Check the state of the provisional space if applicable.
  const Optional<uint32_t>& provisional_index = header.provisional_index;
  bool provisional_space_in_storage = false;
  if (provisional_index.valid()) {
    NvramSpace space;
    switch (SanitizeStorageStatus(
        persistence::LoadSpace(provisional_index.value(), &space))) {
      case storage::Status::kStorageError:
        // Log an error but leave the space marked as allocated. This will allow
        // initialization to complete, so other spaces can be accessed.
        // Operations on the bad space will fail however. The choice of keeping
        // the bad space around (as opposed to dropping it) is intentional:
        //  * Failing noisily reduces the chances of bugs going undetected.
        //  * Keeping the index allocated prevents it from being accidentally
        //    clobbered due to appearing absent after transient storage errors.
        NVRAM_LOG_ERR("Failed to load provisional space 0x%" PRIx32 ".",
                      provisional_index.value());
        provisional_space_in_storage = true;
        break;
      case storage::Status::kNotFound:
        break;
      case storage::Status::kSuccess:
        provisional_space_in_storage = true;
        break;
    }
  }

  // If there are more spaces allocated than this build supports, fail
  // initialization. This may seem a bit drastic, but the alternatives aren't
  // acceptable:
  //  * If we continued with just a subset of the spaces, that may lead to wrong
  //    conclusions about the system state in consumers. Furthermore, consumers
  //    might delete a space to make room and then create a space that appears
  //    free but is present in storage. This would clobber the existing space
  //    data and potentially violate its access control rules.
  //  * We could just try to allocate more memory to hold the larger number of
  //    spaces. That'd render the memory footprint of the NVRAM implementation
  //    unpredictable. One variation that may work is to allow a maximum number
  //    of existing spaces larger than kMaxSpaces, but still within sane limits.
  if (header.allocated_indices.size() > kMaxSpaces) {
    NVRAM_LOG_ERR("Excess spaces %zu in header.",
                  header.allocated_indices.size());
    return false;
  }

  // Initialize the transient space bookkeeping data.
  bool delete_provisional_space = provisional_index.valid();
  for (uint32_t index : header.allocated_indices) {
    if (provisional_index.valid() && provisional_index.value() == index) {
      // The provisional space index refers to a created space. If it isn't
      // valid, pretend it was never created.
      if (!provisional_space_in_storage) {
        continue;
      }

      // The provisional space index corresponds to a created space that is
      // present in storage. Retain the space.
      delete_provisional_space = false;
    }

    spaces_[num_spaces_].index = index;
    spaces_[num_spaces_].write_locked = false;
    spaces_[num_spaces_].read_locked = false;
    ++num_spaces_;
  }

  // If the provisional space data is present in storage, but the index wasn't
  // in |header.allocated_indices|, it refers to half-deleted space. Destroy the
  // space in that case.
  if (delete_provisional_space) {
    switch (SanitizeStorageStatus(
        persistence::DeleteSpace(provisional_index.value()))) {
      case storage::Status::kStorageError:
        NVRAM_LOG_ERR("Failed to delete provisional space 0x%" PRIx32 " data.",
                      provisional_index.value());
        return false;
      case storage::Status::kNotFound:
        // The space isn't present in storage. This may happen if the space
        // deletion succeeded, but the header wasn't written subsequently.
        break;
      case storage::Status::kSuccess:
        break;
    }
  }

  disable_create_ = header.HasFlag(NvramHeader::kFlagDisableCreate);
  initialized_ = true;

  // Write the header to clear the provisional index if necessary. It's actually
  // not a problem if this fails, because the state is consistent regardless. We
  // still do this opportunistically in order to avoid loading the provisional
  // space data for each reboot after a crash.
  if (provisional_index.valid()) {
    WriteHeader(Optional<uint32_t>());
  }

  return true;
}

size_t NvramManager::FindSpace(uint32_t space_index) {
  for (size_t i = 0; i < num_spaces_; ++i) {
    if (spaces_[i].index == space_index) {
      return i;
    }
  }

  return kMaxSpaces;
}

bool NvramManager::LoadSpaceRecord(uint32_t index,
                                   SpaceRecord* space_record,
                                   nvram_result_t* result) {
  space_record->array_index = FindSpace(index);
  if (space_record->array_index == kMaxSpaces) {
    *result = NV_RESULT_SPACE_DOES_NOT_EXIST;
    return false;
  }

  space_record->transient = &spaces_[space_record->array_index];

  switch (SanitizeStorageStatus(
      persistence::LoadSpace(index, &space_record->persistent))) {
    case storage::Status::kStorageError:
      NVRAM_LOG_ERR("Failed to load space 0x%" PRIx32 " data.", index);
      *result = NV_RESULT_INTERNAL_ERROR;
      return false;
    case storage::Status::kNotFound:
      // This should never happen if the header contains the index.
      NVRAM_LOG_ERR("Space index 0x%" PRIx32
                    " present in header, but data missing.",
                    index);
      *result = NV_RESULT_INTERNAL_ERROR;
      return false;
    case storage::Status::kSuccess:
      *result = NV_RESULT_SUCCESS;
      return true;
  }

  *result = NV_RESULT_INTERNAL_ERROR;
  return false;
}

nvram_result_t NvramManager::WriteHeader(Optional<uint32_t> provisional_index) {
  NvramHeader header;
  header.version = NvramHeader::kVersion;
  if (disable_create_) {
    header.SetFlag(NvramHeader::kFlagDisableCreate);
  }

  if (!header.allocated_indices.Resize(num_spaces_)) {
    NVRAM_LOG_ERR("Allocation failure.");
    return NV_RESULT_INTERNAL_ERROR;
  }
  for (size_t i = 0; i < num_spaces_; ++i) {
    header.allocated_indices[i] = spaces_[i].index;
  }

  header.provisional_index = provisional_index;

  if (SanitizeStorageStatus(persistence::StoreHeader(header)) !=
      storage::Status::kSuccess) {
    NVRAM_LOG_ERR("Failed to store header.");
    return NV_RESULT_INTERNAL_ERROR;
  }

  return NV_RESULT_SUCCESS;
}

nvram_result_t NvramManager::WriteSpace(uint32_t index,
                                        const NvramSpace& space) {
  if (SanitizeStorageStatus(persistence::StoreSpace(index, space)) !=
      storage::Status::kSuccess) {
    NVRAM_LOG_ERR("Failed to store space 0x%" PRIx32 ".", index);
    return NV_RESULT_INTERNAL_ERROR;
  }

  return NV_RESULT_SUCCESS;
}

}  // namespace nvram