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