1 /*
2 * Testing operating system specific wrapper code
3 * (C) 2017 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7 
8 #include "tests.h"
9 #include <botan/internal/os_utils.h>
10 
11 // For __ud2 intrinsic
12 #if defined(BOTAN_TARGET_COMPILER_IS_MSVC)
13 #include <intrin.h>
14 #endif
15 
16 namespace Botan_Tests {
17 
18 namespace {
19 
20 /*
21 uint32_t get_process_id();
22 uint64_t get_cpu_cycle_counter();
23 uint64_t get_system_timestamp_ns();
24 size_t get_memory_locking_limit();
25 void* allocate_locked_pages(size_t length);
26 void free_locked_pages(void* ptr, size_t length);
27 int run_cpu_instruction_probe(std::function<int ()> probe_fn);
28 */
29 
30 class OS_Utils_Tests final : public Test
31    {
32    public:
run()33       std::vector<Test::Result> run() override
34          {
35          std::vector<Test::Result> results;
36 
37          results.push_back(test_get_process_id());
38          results.push_back(test_get_cpu_cycle_counter());
39          results.push_back(test_get_high_resolution_clock());
40          results.push_back(test_get_cpu_numbers());
41          results.push_back(test_get_system_timestamp());
42          results.push_back(test_memory_locking());
43          results.push_back(test_cpu_instruction_probe());
44 
45          return results;
46          }
47 
48    private:
49 
test_get_process_id()50       Test::Result test_get_process_id()
51          {
52          Test::Result result("OS::get_process_id");
53 
54          uint32_t pid1 = Botan::OS::get_process_id();
55          uint32_t pid2 = Botan::OS::get_process_id();
56 
57          result.test_eq("PID same across calls", static_cast<size_t>(pid1), static_cast<size_t>(pid2));
58 
59 #if defined(BOTAN_TARGET_OS_IS_INCLUDEOS) || defined(BOTAN_TARGET_OS_IS_LLVM)
60          result.test_eq("PID is expected to be zero on this platform", pid1, size_t(0));
61 #else
62          result.test_ne("PID is non-zero on systems with processes", pid1, 0);
63 #endif
64 
65          return result;
66          }
67 
test_get_cpu_cycle_counter()68       Test::Result test_get_cpu_cycle_counter()
69          {
70          const size_t max_trials = 1024;
71          const size_t max_repeats = 32;
72 
73          Test::Result result("OS::get_cpu_cycle_counter");
74 
75          const uint64_t proc_ts1 = Botan::OS::get_cpu_cycle_counter();
76 
77          if(proc_ts1 == 0)
78             {
79             const uint64_t proc_ts2 = Botan::OS::get_cpu_cycle_counter();
80             result.test_is_eq("Disabled processor timestamp stays at zero", proc_ts1, proc_ts2);
81             return result;
82             }
83 
84          size_t counts = 0;
85          while(counts < max_trials && (Botan::OS::get_cpu_cycle_counter() == proc_ts1))
86             ++counts;
87 
88          result.test_lt("CPU cycle counter eventually changes value", counts, max_repeats);
89 
90          return result;
91          }
92 
test_get_high_resolution_clock()93       Test::Result test_get_high_resolution_clock()
94          {
95          const size_t max_trials = 1024;
96          const size_t max_repeats = 128;
97 
98          Test::Result result("OS::get_high_resolution_clock");
99 
100          // TODO better tests
101          const uint64_t hr_ts1 = Botan::OS::get_high_resolution_clock();
102          result.confirm("high resolution timestamp value is never zero", hr_ts1 != 0);
103 
104          size_t counts = 0;
105          while(counts < max_trials && (Botan::OS::get_high_resolution_clock() == hr_ts1))
106             ++counts;
107 
108          result.test_lt("high resolution clock eventually changes value", counts, max_repeats);
109 
110          return result;
111          }
112 
test_get_cpu_numbers()113       Test::Result test_get_cpu_numbers()
114          {
115          Test::Result result("OS::get_cpu_total/OS::get_cpu_available");
116 
117          size_t tt = Botan::OS::get_cpu_total();
118          size_t ta = Botan::OS::get_cpu_available();
119 
120          result.test_lte("get_cpu_available not greater than total", ta, tt);
121 
122          return result;
123          }
124 
test_get_system_timestamp()125       Test::Result test_get_system_timestamp()
126          {
127          // TODO better tests
128          Test::Result result("OS::get_system_timestamp_ns");
129 
130          uint64_t sys_ts1 = Botan::OS::get_system_timestamp_ns();
131          result.confirm("System timestamp value is never zero", sys_ts1 != 0);
132 
133          // do something that consumes a little time
134          Botan::OS::get_process_id();
135 
136          uint64_t sys_ts2 = Botan::OS::get_system_timestamp_ns();
137 
138          result.confirm("System time moves forward", sys_ts1 <= sys_ts2);
139 
140          return result;
141          }
142 
test_memory_locking()143       Test::Result test_memory_locking()
144          {
145          Test::Result result("OS memory locked pages");
146 
147          // TODO any tests...
148 
149          return result;
150          }
151 
test_cpu_instruction_probe()152       Test::Result test_cpu_instruction_probe()
153          {
154          Test::Result result("OS::run_cpu_instruction_probe");
155 
156          // OS::run_cpu_instruction_probe only implemented for Unix signals or Windows SEH
157 
158          std::function<int ()> ok_fn = []() noexcept -> int { return 5; };
159          const int run_rc = Botan::OS::run_cpu_instruction_probe(ok_fn);
160 
161          if(run_rc == -3)
162             {
163             result.test_note("run_cpu_instruction_probe not implemented on this platform");
164             return {result};
165             }
166 
167          result.confirm("Correct result returned by working probe fn", run_rc == 5);
168 
169          std::function<int ()> crash_probe;
170 
171 #if defined(BOTAN_TARGET_COMPILER_IS_MSVC)
172          crash_probe = []() noexcept -> int { __ud2(); return 3; };
173 
174 #elif defined(BOTAN_USE_GCC_INLINE_ASM)
175 
176 #if defined(BOTAN_TARGET_CPU_IS_X86_FAMILY)
177          crash_probe = []() noexcept -> int { asm volatile("ud2"); return 3; };
178 
179 #elif defined(BOTAN_TARGET_CPU_IS_ARM_FAMILY)
180          //ARM: asm volatile (".word 0xf7f0a000\n");
181          // illegal instruction in both ARM and Thumb modes
182          crash_probe = []() noexcept -> int { asm volatile(".word 0xe7f0def0\n"); return 3; };
183 
184 #else
185          /*
186          PPC: "The instruction with primary opcode 0, when the instruction does not consist
187          entirely of binary zeros"
188          Others ?
189          */
190 #endif
191 
192 #endif
193 
194          if(crash_probe)
195             {
196             const int crash_rc = Botan::OS::run_cpu_instruction_probe(crash_probe);
197             result.confirm("Result for function executing undefined opcode", crash_rc < 0);
198             }
199 
200          return result;
201          }
202    };
203 
204 BOTAN_REGISTER_TEST("utils", "os_utils", OS_Utils_Tests);
205 
206 }
207 
208 }
209