Botan  2.4.0
Crypto and TLS for C++11
os_utils.cpp
Go to the documentation of this file.
1 /*
2 * OS and machine specific utility functions
3 * (C) 2015,2016,2017 Jack Lloyd
4 * (C) 2016 Daniel Neus
5 *
6 * Botan is released under the Simplified BSD License (see license.txt)
7 */
8 
9 #include <botan/internal/os_utils.h>
10 #include <botan/cpuid.h>
11 #include <botan/exceptn.h>
12 #include <botan/mem_ops.h>
13 
14 #include <chrono>
15 #include <cstdlib>
16 
17 #if defined(BOTAN_TARGET_OS_HAS_EXPLICIT_BZERO)
18  #include <string.h>
19 #endif
20 
21 #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX)
22  #include <sys/types.h>
23  #include <sys/resource.h>
24  #include <sys/mman.h>
25  #include <signal.h>
26  #include <setjmp.h>
27  #include <unistd.h>
28  #include <errno.h>
29 #elif defined(BOTAN_TARGET_OS_TYPE_IS_WINDOWS)
30  #define NOMINMAX 1
31  #include <windows.h>
32 #endif
33 
34 namespace Botan {
35 
36 // Not defined in OS namespace for historical reasons
37 void secure_scrub_memory(void* ptr, size_t n)
38  {
39 #if defined(BOTAN_TARGET_OS_HAS_RTLSECUREZEROMEMORY)
40  ::RtlSecureZeroMemory(ptr, n);
41 
42 #elif defined(BOTAN_TARGET_OS_HAS_EXPLICIT_BZERO)
43  ::explicit_bzero(ptr, n);
44 
45 #elif defined(BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO) && (BOTAN_USE_VOLATILE_MEMSET_FOR_ZERO == 1)
46  /*
47  Call memset through a static volatile pointer, which the compiler
48  should not elide. This construct should be safe in conforming
49  compilers, but who knows. I did confirm that on x86-64 GCC 6.1 and
50  Clang 3.8 both create code that saves the memset address in the
51  data segment and uncondtionally loads and jumps to that address.
52  */
53  static void* (*const volatile memset_ptr)(void*, int, size_t) = std::memset;
54  (memset_ptr)(ptr, 0, n);
55 #else
56 
57  volatile uint8_t* p = reinterpret_cast<volatile uint8_t*>(ptr);
58 
59  for(size_t i = 0; i != n; ++i)
60  p[i] = 0;
61 #endif
62  }
63 
65  {
66 #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX)
67  return ::getpid();
68 #elif defined(BOTAN_TARGET_OS_IS_WINDOWS) || defined(BOTAN_TARGET_OS_IS_MINGW)
69  return ::GetCurrentProcessId();
70 #elif defined(BOTAN_TARGET_OS_TYPE_IS_UNIKERNEL) || defined(BOTAN_TARGET_OS_IS_LLVM)
71  return 0; // truly no meaningful value
72 #else
73  #error "Missing get_process_id"
74 #endif
75  }
76 
78  {
79  uint64_t rtc = 0;
80 
81 #if defined(BOTAN_TARGET_OS_HAS_QUERY_PERF_COUNTER)
82  LARGE_INTEGER tv;
83  ::QueryPerformanceCounter(&tv);
84  rtc = tv.QuadPart;
85 
86 #elif defined(BOTAN_USE_GCC_INLINE_ASM)
87 
88 #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY)
89 
90  if(CPUID::has_rdtsc())
91  {
92  uint32_t rtc_low = 0, rtc_high = 0;
93  asm volatile("rdtsc" : "=d" (rtc_high), "=a" (rtc_low));
94  rtc = (static_cast<uint64_t>(rtc_high) << 32) | rtc_low;
95  }
96 
97 #elif defined(BOTAN_TARGET_ARCH_IS_PPC64)
98  uint32_t rtc_low = 0, rtc_high = 0;
99  asm volatile("mftbu %0; mftb %1" : "=r" (rtc_high), "=r" (rtc_low));
100 
101  /*
102  qemu-ppc seems to not support mftb instr, it always returns zero.
103  If both time bases are 0, assume broken and return another clock.
104  */
105  if(rtc_high > 0 || rtc_low > 0)
106  {
107  rtc = (static_cast<uint64_t>(rtc_high) << 32) | rtc_low;
108  }
109 
110 #elif defined(BOTAN_TARGET_ARCH_IS_ALPHA)
111  asm volatile("rpcc %0" : "=r" (rtc));
112 
113  // OpenBSD does not trap access to the %tick register
114 #elif defined(BOTAN_TARGET_ARCH_IS_SPARC64) && !defined(BOTAN_TARGET_OS_IS_OPENBSD)
115  asm volatile("rd %%tick, %0" : "=r" (rtc));
116 
117 #elif defined(BOTAN_TARGET_ARCH_IS_IA64)
118  asm volatile("mov %0=ar.itc" : "=r" (rtc));
119 
120 #elif defined(BOTAN_TARGET_ARCH_IS_S390X)
121  asm volatile("stck 0(%0)" : : "a" (&rtc) : "memory", "cc");
122 
123 #elif defined(BOTAN_TARGET_ARCH_IS_HPPA)
124  asm volatile("mfctl 16,%0" : "=r" (rtc)); // 64-bit only?
125 
126 #else
127  //#warning "OS::get_processor_timestamp not implemented"
128 #endif
129 
130 #endif
131 
132  return rtc;
133  }
134 
136  {
137  if(uint64_t cpu_clock = OS::get_processor_timestamp())
138  return cpu_clock;
139 
140  /*
141  If we got here either we either don't have an asm instruction
142  above, or (for x86) RDTSC is not available at runtime. Try some
143  clock_gettimes and return the first one that works, or otherwise
144  fall back to std::chrono.
145  */
146 
147 #if defined(BOTAN_TARGET_OS_HAS_CLOCK_GETTIME)
148 
149  // The ordering here is somewhat arbitrary...
150  const clockid_t clock_types[] = {
151 #if defined(CLOCK_MONOTONIC_HR)
152  CLOCK_MONOTONIC_HR,
153 #endif
154 #if defined(CLOCK_MONOTONIC_RAW)
155  CLOCK_MONOTONIC_RAW,
156 #endif
157 #if defined(CLOCK_MONOTONIC)
158  CLOCK_MONOTONIC,
159 #endif
160 #if defined(CLOCK_PROCESS_CPUTIME_ID)
161  CLOCK_PROCESS_CPUTIME_ID,
162 #endif
163 #if defined(CLOCK_THREAD_CPUTIME_ID)
164  CLOCK_THREAD_CPUTIME_ID,
165 #endif
166  };
167 
168  for(clockid_t clock : clock_types)
169  {
170  struct timespec ts;
171  if(::clock_gettime(clock, &ts) == 0)
172  {
173  return (static_cast<uint64_t>(ts.tv_sec) * 1000000000) + static_cast<uint64_t>(ts.tv_nsec);
174  }
175  }
176 #endif
177 
178  // Plain C++11 fallback
179  auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
180  return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
181  }
182 
184  {
185 #if defined(BOTAN_TARGET_OS_HAS_CLOCK_GETTIME)
186  struct timespec ts;
187  if(::clock_gettime(CLOCK_REALTIME, &ts) == 0)
188  {
189  return (static_cast<uint64_t>(ts.tv_sec) * 1000000000) + static_cast<uint64_t>(ts.tv_nsec);
190  }
191 #endif
192 
193  auto now = std::chrono::system_clock::now().time_since_epoch();
194  return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
195  }
196 
198  {
199 #if defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK)
200  /*
201  * Linux defaults to only 64 KiB of mlockable memory per process
202  * (too small) but BSDs offer a small fraction of total RAM (more
203  * than we need). Bound the total mlock size to 512 KiB which is
204  * enough to run the entire test suite without spilling to non-mlock
205  * memory (and thus presumably also enough for many useful
206  * programs), but small enough that we should not cause problems
207  * even if many processes are mlocking on the same machine.
208  */
209  size_t mlock_requested = BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB;
210 
211  /*
212  * Allow override via env variable
213  */
214  if(const char* env = std::getenv("BOTAN_MLOCK_POOL_SIZE"))
215  {
216  try
217  {
218  const size_t user_req = std::stoul(env, nullptr);
219  mlock_requested = std::min(user_req, mlock_requested);
220  }
221  catch(std::exception&) { /* ignore it */ }
222  }
223 
224 #if defined(RLIMIT_MEMLOCK)
225  if(mlock_requested > 0)
226  {
227  struct ::rlimit limits;
228 
229  ::getrlimit(RLIMIT_MEMLOCK, &limits);
230 
231  if(limits.rlim_cur < limits.rlim_max)
232  {
233  limits.rlim_cur = limits.rlim_max;
234  ::setrlimit(RLIMIT_MEMLOCK, &limits);
235  ::getrlimit(RLIMIT_MEMLOCK, &limits);
236  }
237 
238  return std::min<size_t>(limits.rlim_cur, mlock_requested * 1024);
239  }
240 #else
241  /*
242  * If RLIMIT_MEMLOCK is not defined, likely the OS does not support
243  * unprivileged mlock calls.
244  */
245  return 0;
246 #endif
247 
248 #elif defined(BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK) && defined(BOTAN_BUILD_COMPILER_IS_MSVC)
249  SIZE_T working_min = 0, working_max = 0;
250  DWORD working_flags = 0;
251  if(!::GetProcessWorkingSetSizeEx(::GetCurrentProcess(), &working_min, &working_max, &working_flags))
252  {
253  return 0;
254  }
255 
256  SYSTEM_INFO sSysInfo;
257  ::GetSystemInfo(&sSysInfo);
258 
259  // According to Microsoft MSDN:
260  // The maximum number of pages that a process can lock is equal to the number of pages in its minimum working set minus a small overhead
261  // In the book "Windows Internals Part 2": the maximum lockable pages are minimum working set size - 8 pages
262  // But the information in the book seems to be inaccurate/outdated
263  // I've tested this on Windows 8.1 x64, Windows 10 x64 and Windows 7 x86
264  // On all three OS the value is 11 instead of 8
265  size_t overhead = sSysInfo.dwPageSize * 11ULL;
266  if(working_min > overhead)
267  {
268  size_t lockable_bytes = working_min - overhead;
269  if(lockable_bytes < (BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB * 1024ULL))
270  {
271  return lockable_bytes;
272  }
273  else
274  {
275  return BOTAN_MLOCK_ALLOCATOR_MAX_LOCKED_KB * 1024ULL;
276  }
277  }
278 #endif
279 
280  return 0;
281  }
282 
283 void* OS::allocate_locked_pages(size_t length)
284  {
285 #if defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK)
286 
287 #if !defined(MAP_NOCORE)
288  #define MAP_NOCORE 0
289 #endif
290 
291 #if !defined(MAP_ANONYMOUS)
292  #define MAP_ANONYMOUS MAP_ANON
293 #endif
294 
295  void* ptr = ::mmap(nullptr,
296  length,
297  PROT_READ | PROT_WRITE,
298  MAP_ANONYMOUS | MAP_SHARED | MAP_NOCORE,
299  /*fd*/-1,
300  /*offset*/0);
301 
302  if(ptr == MAP_FAILED)
303  {
304  return nullptr;
305  }
306 
307 #if defined(MADV_DONTDUMP)
308  ::madvise(ptr, length, MADV_DONTDUMP);
309 #endif
310 
311  if(::mlock(ptr, length) != 0)
312  {
313  ::munmap(ptr, length);
314  return nullptr; // failed to lock
315  }
316 
317  ::memset(ptr, 0, length);
318 
319  return ptr;
320 #elif defined BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK
321  LPVOID ptr = ::VirtualAlloc(nullptr, length, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
322  if(!ptr)
323  {
324  return nullptr;
325  }
326 
327  if(::VirtualLock(ptr, length) == 0)
328  {
329  ::VirtualFree(ptr, 0, MEM_RELEASE);
330  return nullptr; // failed to lock
331  }
332 
333  return ptr;
334 #else
335  BOTAN_UNUSED(length);
336  return nullptr; /* not implemented */
337 #endif
338  }
339 
340 void OS::free_locked_pages(void* ptr, size_t length)
341  {
342  if(ptr == nullptr || length == 0)
343  return;
344 
345 #if defined(BOTAN_TARGET_OS_HAS_POSIX_MLOCK)
346  secure_scrub_memory(ptr, length);
347  ::munlock(ptr, length);
348  ::munmap(ptr, length);
349 #elif defined BOTAN_TARGET_OS_HAS_VIRTUAL_LOCK
350  secure_scrub_memory(ptr, length);
351  ::VirtualUnlock(ptr, length);
352  ::VirtualFree(ptr, 0, MEM_RELEASE);
353 #else
354  // Invalid argument because no way this pointer was allocated by us
355  throw Invalid_Argument("Invalid ptr to free_locked_pages");
356 #endif
357  }
358 
359 #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX)
360 namespace {
361 
362 static ::sigjmp_buf g_sigill_jmp_buf;
363 
364 void botan_sigill_handler(int)
365  {
366  siglongjmp(g_sigill_jmp_buf, /*non-zero return value*/1);
367  }
368 
369 }
370 #endif
371 
372 int OS::run_cpu_instruction_probe(std::function<int ()> probe_fn)
373  {
374  volatile int probe_result = -3;
375 
376 #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX)
377  struct sigaction old_sigaction;
378  struct sigaction sigaction;
379 
380  sigaction.sa_handler = botan_sigill_handler;
381  sigemptyset(&sigaction.sa_mask);
382  sigaction.sa_flags = 0;
383 
384  int rc = ::sigaction(SIGILL, &sigaction, &old_sigaction);
385 
386  if(rc != 0)
387  throw Exception("run_cpu_instruction_probe sigaction failed");
388 
389  rc = sigsetjmp(g_sigill_jmp_buf, /*save sigs*/1);
390 
391  if(rc == 0)
392  {
393  // first call to sigsetjmp
394  probe_result = probe_fn();
395  }
396  else if(rc == 1)
397  {
398  // non-local return from siglongjmp in signal handler: return error
399  probe_result = -1;
400  }
401 
402  // Restore old SIGILL handler, if any
403  rc = ::sigaction(SIGILL, &old_sigaction, nullptr);
404  if(rc != 0)
405  throw Exception("run_cpu_instruction_probe sigaction restore failed");
406 
407 #elif defined(BOTAN_TARGET_OS_IS_WINDOWS) && defined(BOTAN_TARGET_COMPILER_IS_MSVC)
408 
409  // Windows SEH
410  __try
411  {
412  probe_result = probe_fn();
413  }
414  __except(::GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION ?
415  EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
416  {
417  probe_result = -1;
418  }
419 
420 #endif
421 
422  return probe_result;
423  }
424 
425 }
int BOTAN_TEST_API run_cpu_instruction_probe(std::function< int()> probe_fn)
Definition: os_utils.cpp:372
uint32_t BOTAN_TEST_API get_process_id()
Definition: os_utils.cpp:64
void * allocate_locked_pages(size_t length)
Definition: os_utils.cpp:283
size_t get_memory_locking_limit()
Definition: os_utils.cpp:197
uint64_t BOTAN_TEST_API get_processor_timestamp()
Definition: os_utils.cpp:77
uint64_t BOTAN_TEST_API get_system_timestamp_ns()
Definition: os_utils.cpp:183
Definition: alg_id.cpp:13
#define BOTAN_UNUSED(...)
Definition: assert.h:106
void secure_scrub_memory(void *ptr, size_t n)
Definition: os_utils.cpp:37
void free_locked_pages(void *ptr, size_t length)
Definition: os_utils.cpp:340
uint64_t BOTAN_TEST_API get_high_resolution_clock()
Definition: os_utils.cpp:135