xref: /aosp_15_r20/external/libbrillo/brillo/secure_allocator.h (revision 1a96fba65179ea7d3f56207137718607415c5953)
1*1a96fba6SXin Li // Copyright 2018 The Chromium OS Authors. All rights reserved.
2*1a96fba6SXin Li // Use of this source code is governed by a BSD-style license that can be
3*1a96fba6SXin Li // found in the LICENSE file.
4*1a96fba6SXin Li 
5*1a96fba6SXin Li #ifndef LIBBRILLO_BRILLO_SECURE_ALLOCATOR_H_
6*1a96fba6SXin Li #define LIBBRILLO_BRILLO_SECURE_ALLOCATOR_H_
7*1a96fba6SXin Li 
8*1a96fba6SXin Li #include <errno.h>
9*1a96fba6SXin Li #include <sys/mman.h>
10*1a96fba6SXin Li #include <unistd.h>
11*1a96fba6SXin Li 
12*1a96fba6SXin Li #include <limits>
13*1a96fba6SXin Li #include <memory>
14*1a96fba6SXin Li 
15*1a96fba6SXin Li #include <base/callback_helpers.h>
16*1a96fba6SXin Li #include <base/logging.h>
17*1a96fba6SXin Li #include <brillo/brillo_export.h>
18*1a96fba6SXin Li 
19*1a96fba6SXin Li namespace brillo {
20*1a96fba6SXin Li // SecureAllocator is a stateless derivation of std::allocator that clears
21*1a96fba6SXin Li // the contents of the object on deallocation. Additionally, to prevent the
22*1a96fba6SXin Li // memory from being leaked, we use the following defensive mechanisms:
23*1a96fba6SXin Li //
24*1a96fba6SXin Li // 1. Use page-aligned memory so that it can be locked (therefore, use mmap()
25*1a96fba6SXin Li //    instead of malloc()). Note that mlock()s are not inherited over fork(),
26*1a96fba6SXin Li //
27*1a96fba6SXin Li // 2. Always allocate memory in multiples of pages: this adds a memory overhead
28*1a96fba6SXin Li //    of ~1 page for each object. Moreover, the extra memory is not available
29*1a96fba6SXin Li //    for the allocated object to expand into: the container expects that the
30*1a96fba6SXin Li //    memory allocated to it matches the size set in reserve().
31*1a96fba6SXin Li // TODO(sarthakkukreti): Figure out if it is possible to propagate the real
32*1a96fba6SXin Li // capacity to the container without an intrusive change to the STL.
33*1a96fba6SXin Li // [Example: allow __recommend() override in allocators for containers.]
34*1a96fba6SXin Li //
35*1a96fba6SXin Li // 3. Mark the memory segments as undumpable, unmergeable.
36*1a96fba6SXin Li //
37*1a96fba6SXin Li // 4. Use MADV_WIPEONFORK:
38*1a96fba6SXin Li //    this results in a new anonymous vma instead of copying over the contents
39*1a96fba6SXin Li //    of the secure object after a fork(). By default [MADV_DOFORK], the vma is
40*1a96fba6SXin Li //    marked as copy-on-write, and the first process which writes to the secure
41*1a96fba6SXin Li //    object after fork get a new copy. This may break the security guarantees
42*1a96fba6SXin Li //    setup above. Another alternative is to use MADV_DONTFORK which results in
43*1a96fba6SXin Li //    the memory mapping not getting copied over to child process at all: this
44*1a96fba6SXin Li //    may result in cases where if the child process gets segmentation faults
45*1a96fba6SXin Li //    on attempts to access virtual addresses in the secure object's address
46*1a96fba6SXin Li //    range,
47*1a96fba6SXin Li //
48*1a96fba6SXin Li //    With MADV_WIPEONFORK, the child processes can access the secure object
49*1a96fba6SXin Li //    memory safely, but the contents of the secure object appear as zero to
50*1a96fba6SXin Li //    the child process. Note that threads share the virtual address space and
51*1a96fba6SXin Li //    secure objects would be transparent across threads.
52*1a96fba6SXin Li // TODO(sarthakkukreti): Figure out patterns to pass secure data over fork().
53*1a96fba6SXin Li template <typename T>
54*1a96fba6SXin Li class BRILLO_PRIVATE SecureAllocator : public std::allocator<T> {
55*1a96fba6SXin Li  public:
56*1a96fba6SXin Li   using typename std::allocator<T>::size_type;
57*1a96fba6SXin Li   using typename std::allocator<T>::value_type;
58*1a96fba6SXin Li 
59*1a96fba6SXin Li   // Constructors that wrap over std::allocator.
60*1a96fba6SXin Li   // Make sure that the allocator's static members are only allocated once.
SecureAllocator()61*1a96fba6SXin Li   SecureAllocator() noexcept : std::allocator<T>() {}
SecureAllocator(const SecureAllocator & other)62*1a96fba6SXin Li   SecureAllocator(const SecureAllocator& other) noexcept
63*1a96fba6SXin Li       : std::allocator<T>(other) {}
64*1a96fba6SXin Li 
65*1a96fba6SXin Li   template <class U>
SecureAllocator(const SecureAllocator<U> & other)66*1a96fba6SXin Li   SecureAllocator(const SecureAllocator<U>& other) noexcept
67*1a96fba6SXin Li       : std::allocator<T>(other) {}
68*1a96fba6SXin Li 
69*1a96fba6SXin Li   template <typename U> struct rebind {
70*1a96fba6SXin Li     typedef SecureAllocator<U> other;
71*1a96fba6SXin Li   };
72*1a96fba6SXin Li 
73*1a96fba6SXin Li   // Return cached max_size. Deprecated in C++17, removed in C++20.
max_size()74*1a96fba6SXin Li   size_type max_size() const { return max_size_; }
75*1a96fba6SXin Li 
76*1a96fba6SXin Li   // Allocation: allocate ceil(size/pagesize) for holding the data.
77*1a96fba6SXin Li   value_type* allocate(size_type n, value_type* hint = nullptr) {
78*1a96fba6SXin Li     value_type* buffer = nullptr;
79*1a96fba6SXin Li     // Check if n can be theoretically allocated.
80*1a96fba6SXin Li     CHECK_LT(n, max_size());
81*1a96fba6SXin Li 
82*1a96fba6SXin Li     // std::allocator is expected to throw a std::bad_alloc on failing to
83*1a96fba6SXin Li     // allocate the memory correctly. Instead of returning a nullptr, which
84*1a96fba6SXin Li     // confuses the standard template library, use CHECK(false) to crash on
85*1a96fba6SXin Li     // the failure path.
86*1a96fba6SXin Li     base::ScopedClosureRunner fail_on_allocation_error(base::Bind([]() {
87*1a96fba6SXin Li       PLOG(ERROR) << "Failed to allocate secure memory";
88*1a96fba6SXin Li       CHECK(false);
89*1a96fba6SXin Li     }));
90*1a96fba6SXin Li 
91*1a96fba6SXin Li     // Check if n = 0: there's nothing to allocate;
92*1a96fba6SXin Li     if (n == 0)
93*1a96fba6SXin Li       return nullptr;
94*1a96fba6SXin Li 
95*1a96fba6SXin Li     // Calculate the page-aligned buffer size.
96*1a96fba6SXin Li     size_type buffer_size = CalculatePageAlignedBufferSize(n);
97*1a96fba6SXin Li 
98*1a96fba6SXin Li     // Memory locking granularity is per-page: mmap ceil(size/page size) pages.
99*1a96fba6SXin Li     buffer = reinterpret_cast<value_type*>(
100*1a96fba6SXin Li         mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE,
101*1a96fba6SXin Li              MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
102*1a96fba6SXin Li     if (buffer == MAP_FAILED)
103*1a96fba6SXin Li       return nullptr;
104*1a96fba6SXin Li 
105*1a96fba6SXin Li     // Lock buffer into physical memory.
106*1a96fba6SXin Li     if (mlock(buffer, buffer_size)) {
107*1a96fba6SXin Li       CHECK_NE(errno, ENOMEM) << "It is likely that SecureAllocator have "
108*1a96fba6SXin Li                                  "exceeded the RLIMIT_MEMLOCK limit";
109*1a96fba6SXin Li       return nullptr;
110*1a96fba6SXin Li     }
111*1a96fba6SXin Li 
112*1a96fba6SXin Li     // Mark memory as non dumpable in a core dump.
113*1a96fba6SXin Li     if (madvise(buffer, buffer_size, MADV_DONTDUMP))
114*1a96fba6SXin Li       return nullptr;
115*1a96fba6SXin Li 
116*1a96fba6SXin Li     // Mark memory as non mergeable with another page, even if the contents
117*1a96fba6SXin Li     // are the same.
118*1a96fba6SXin Li     if (madvise(buffer, buffer_size, MADV_UNMERGEABLE)) {
119*1a96fba6SXin Li       // MADV_UNMERGEABLE is only available if the kernel has been configured
120*1a96fba6SXin Li       // with CONFIG_KSM set. If the CONFIG_KSM flag has not been set, then
121*1a96fba6SXin Li       // pages are not mergeable so this madvise option is not necessary.
122*1a96fba6SXin Li       //
123*1a96fba6SXin Li       // In the case where CONFIG_KSM is not set, EINVAL is the error set.
124*1a96fba6SXin Li       // Since this error value is expected in some cases, we don't return a
125*1a96fba6SXin Li       // nullptr.
126*1a96fba6SXin Li       if (errno != EINVAL)
127*1a96fba6SXin Li         return nullptr;
128*1a96fba6SXin Li     }
129*1a96fba6SXin Li 
130*1a96fba6SXin Li     // Make this mapping available to child processes but don't copy data from
131*1a96fba6SXin Li     // the secure object's pages during fork. With MADV_DONTFORK, the
132*1a96fba6SXin Li     // vma is not mapped in the child process which leads to segmentation
133*1a96fba6SXin Li     // faults if the child process tries to access this address. For example,
134*1a96fba6SXin Li     // if the parent process creates a SecureObject, forks() and the child
135*1a96fba6SXin Li     // process tries to call the destructor at the virtual address.
136*1a96fba6SXin Li     if (madvise(buffer, buffer_size, MADV_WIPEONFORK))
137*1a96fba6SXin Li       return nullptr;
138*1a96fba6SXin Li 
139*1a96fba6SXin Li     ignore_result(fail_on_allocation_error.Release());
140*1a96fba6SXin Li 
141*1a96fba6SXin Li     // Allocation was successful.
142*1a96fba6SXin Li     return buffer;
143*1a96fba6SXin Li   }
144*1a96fba6SXin Li 
145*1a96fba6SXin Li   // Destroy object before deallocation. Deprecated in C++17, removed in C++20.
146*1a96fba6SXin Li   // After destroying the object, clear the contents of where the object was
147*1a96fba6SXin Li   // stored.
148*1a96fba6SXin Li   template <class U>
destroy(U * p)149*1a96fba6SXin Li   void destroy(U* p) {
150*1a96fba6SXin Li     // Return if the pointer is invalid.
151*1a96fba6SXin Li     if (!p)
152*1a96fba6SXin Li       return;
153*1a96fba6SXin Li     std::allocator<U>::destroy(p);
154*1a96fba6SXin Li     clear_contents(p, sizeof(U));
155*1a96fba6SXin Li   }
156*1a96fba6SXin Li 
deallocate(value_type * p,size_type n)157*1a96fba6SXin Li   virtual void deallocate(value_type* p, size_type n) {
158*1a96fba6SXin Li     // Check if n can be theoretically deallocated.
159*1a96fba6SXin Li     CHECK_LT(n, max_size());
160*1a96fba6SXin Li 
161*1a96fba6SXin Li     // Check if n = 0 or p is a nullptr: there's nothing to deallocate;
162*1a96fba6SXin Li     if (n == 0 || !p)
163*1a96fba6SXin Li       return;
164*1a96fba6SXin Li 
165*1a96fba6SXin Li     // Calculate the page-aligned buffer size.
166*1a96fba6SXin Li     size_type buffer_size = CalculatePageAlignedBufferSize(n);
167*1a96fba6SXin Li 
168*1a96fba6SXin Li     clear_contents(p, buffer_size);
169*1a96fba6SXin Li     munlock(p, buffer_size);
170*1a96fba6SXin Li     munmap(p, buffer_size);
171*1a96fba6SXin Li   }
172*1a96fba6SXin Li 
173*1a96fba6SXin Li  protected:
174*1a96fba6SXin Li // Force memset to not be optimized out.
175*1a96fba6SXin Li // Original source commit: 31b02653c2560f8331934e879263beda44c6cc76
176*1a96fba6SXin Li // Repo: https://android.googlesource.com/platform/external/minijail
177*1a96fba6SXin Li #if defined(__clang__)
178*1a96fba6SXin Li #define __attribute_no_opt __attribute__((optnone))
179*1a96fba6SXin Li #else
180*1a96fba6SXin Li #define __attribute_no_opt __attribute__((__optimize__(0)))
181*1a96fba6SXin Li #endif
182*1a96fba6SXin Li 
183*1a96fba6SXin Li   // Zero-out all bytes in the allocated buffer.
clear_contents(value_type * v,size_type n)184*1a96fba6SXin Li   virtual void __attribute_no_opt clear_contents(value_type* v, size_type n) {
185*1a96fba6SXin Li     if (!v)
186*1a96fba6SXin Li       return;
187*1a96fba6SXin Li     memset(v, 0, n);
188*1a96fba6SXin Li   }
189*1a96fba6SXin Li 
190*1a96fba6SXin Li #undef __attribute_no_opt
191*1a96fba6SXin Li 
192*1a96fba6SXin Li  private:
193*1a96fba6SXin Li   // Calculate the page-aligned buffer size.
CalculatePageAlignedBufferSize(size_type n)194*1a96fba6SXin Li   size_t CalculatePageAlignedBufferSize(size_type n) {
195*1a96fba6SXin Li     size_type real_size = n * sizeof(value_type);
196*1a96fba6SXin Li     size_type page_aligned_remainder = real_size % page_size_;
197*1a96fba6SXin Li     size_type padding =
198*1a96fba6SXin Li         page_aligned_remainder != 0 ? page_size_ - page_aligned_remainder : 0;
199*1a96fba6SXin Li     return real_size + padding;
200*1a96fba6SXin Li   }
201*1a96fba6SXin Li 
CalculatePageSize()202*1a96fba6SXin Li   static size_t CalculatePageSize() {
203*1a96fba6SXin Li     long ret = sysconf(_SC_PAGESIZE);  // NOLINT [runtime/int]
204*1a96fba6SXin Li 
205*1a96fba6SXin Li     // Initialize page size.
206*1a96fba6SXin Li     CHECK_GT(ret, 0L);
207*1a96fba6SXin Li     return ret;
208*1a96fba6SXin Li   }
209*1a96fba6SXin Li 
210*1a96fba6SXin Li   // Since the allocator reuses page size and max size consistently,
211*1a96fba6SXin Li   // cache these values initially and reuse.
GetMaxSizeForType(size_t page_size)212*1a96fba6SXin Li   static size_t GetMaxSizeForType(size_t page_size) {
213*1a96fba6SXin Li     // Initialize max size that can be theoretically allocated.
214*1a96fba6SXin Li     // Calculate the max size that is page-aligned.
215*1a96fba6SXin Li     size_t max_theoretical_size = std::numeric_limits<size_type>::max();
216*1a96fba6SXin Li     size_t max_page_aligned_size =
217*1a96fba6SXin Li         max_theoretical_size - (max_theoretical_size % page_size);
218*1a96fba6SXin Li 
219*1a96fba6SXin Li     return max_page_aligned_size / sizeof(value_type);
220*1a96fba6SXin Li   }
221*1a96fba6SXin Li 
222*1a96fba6SXin Li   // Page size on system.
223*1a96fba6SXin Li   static const size_type page_size_;
224*1a96fba6SXin Li   // Max theoretical count for type on system.
225*1a96fba6SXin Li   static const size_type max_size_;
226*1a96fba6SXin Li };
227*1a96fba6SXin Li 
228*1a96fba6SXin Li // Inline definitions are only allowed for static const members with integral
229*1a96fba6SXin Li // constexpr initializers, define static members of SecureAllocator types here.
230*1a96fba6SXin Li template <typename T>
231*1a96fba6SXin Li const typename SecureAllocator<T>::size_type SecureAllocator<T>::page_size_ =
232*1a96fba6SXin Li     SecureAllocator<T>::CalculatePageSize();
233*1a96fba6SXin Li 
234*1a96fba6SXin Li template <typename T>
235*1a96fba6SXin Li const typename SecureAllocator<T>::size_type SecureAllocator<T>::max_size_ =
236*1a96fba6SXin Li     SecureAllocator<T>::GetMaxSizeForType(SecureAllocator<T>::page_size_);
237*1a96fba6SXin Li 
238*1a96fba6SXin Li }  // namespace brillo
239*1a96fba6SXin Li 
240*1a96fba6SXin Li #endif  // LIBBRILLO_BRILLO_SECURE_ALLOCATOR_H_
241