db_main.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /* Copyright (C) 2014 Stony Brook University
  2. This file is part of Graphene Library OS.
  3. Graphene Library OS is free software: you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public License
  5. as published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. Graphene Library OS is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  13. /*
  14. * db_main.c
  15. *
  16. * This file contains the main function of the PAL loader, which loads and
  17. * processes environment, arguments and manifest.
  18. */
  19. #include "api.h"
  20. #include "bogomips.h"
  21. #include "pal.h"
  22. #include "pal_debug.h"
  23. #include "pal_defs.h"
  24. #include "pal_error.h"
  25. #include "pal_internal.h"
  26. #include "pal_linux.h"
  27. #include "pal_linux_defs.h"
  28. #include "pal_security.h"
  29. #include <asm/ioctls.h>
  30. #include <asm/mman.h>
  31. #include <elf/elf.h>
  32. #include <sysdeps/generic/ldsodefs.h>
  33. #include "ecall_types.h"
  34. #include "enclave_pages.h"
  35. #define RTLD_BOOTSTRAP
  36. #define _ENTRY enclave_entry
  37. struct pal_linux_state linux_state;
  38. struct pal_sec pal_sec;
  39. size_t g_page_size = PRESET_PAGESIZE;
  40. unsigned long _DkGetPagesize (void)
  41. {
  42. return g_page_size;
  43. }
  44. unsigned long _DkGetAllocationAlignment (void)
  45. {
  46. return g_page_size;
  47. }
  48. void _DkGetAvailableUserAddressRange (PAL_PTR * start, PAL_PTR * end,
  49. PAL_PTR * hole_start, PAL_PTR * hole_end)
  50. {
  51. *start = (PAL_PTR)pal_sec.heap_min;
  52. *end = (PAL_PTR)get_enclave_heap_top();
  53. /* FIXME: hack to keep some heap for internal PAL objects allocated at runtime (recall that
  54. * LibOS does not keep track of PAL memory, so without this hack it could overwrite internal
  55. * PAL memory). This hack is probabilistic and brittle. */
  56. *end = SATURATED_P_SUB(*end, 2 * 1024 * g_page_size, *start); /* 8MB reserved for PAL stuff */
  57. if (*end <= *start) {
  58. SGX_DBG(DBG_E, "Not enough enclave memory, please increase enclave size!\n");
  59. ocall_exit(1, /*is_exitgroup=*/true);
  60. }
  61. *hole_start = SATURATED_P_SUB(pal_sec.exec_addr, MEMORY_GAP, *start);
  62. *hole_end = SATURATED_P_ADD(pal_sec.exec_addr + pal_sec.exec_size, MEMORY_GAP, *end);
  63. }
  64. PAL_NUM _DkGetProcessId (void)
  65. {
  66. return linux_state.process_id;
  67. }
  68. PAL_NUM _DkGetHostId (void)
  69. {
  70. return 0;
  71. }
  72. #include "elf-x86_64.h"
  73. #include "dynamic_link.h"
  74. #include <asm/errno.h>
  75. void setup_pal_map (struct link_map * map);
  76. static struct link_map pal_map;
  77. void init_untrusted_slab_mgr(void);
  78. int init_enclave(void);
  79. int init_enclave_key(void);
  80. int init_child_process(PAL_HANDLE* parent_handle);
  81. void init_cpuid(void);
  82. /*
  83. * Creates a dummy file handle with the given name.
  84. *
  85. * The handle is not backed by any file. Reads will return EOF and writes will
  86. * fail.
  87. */
  88. static PAL_HANDLE setup_dummy_file_handle (const char * name)
  89. {
  90. if (!strstartswith_static(name, URI_PREFIX_FILE))
  91. return NULL;
  92. name += URI_PREFIX_FILE_LEN;
  93. size_t len = strlen(name) + 1;
  94. PAL_HANDLE handle = malloc(HANDLE_SIZE(file) + len);
  95. SET_HANDLE_TYPE(handle, file);
  96. HANDLE_HDR(handle)->flags |= RFD(0);
  97. handle->file.fd = PAL_IDX_POISON;
  98. char * path = (void *) handle + HANDLE_SIZE(file);
  99. int ret = get_norm_path(name, path, &len);
  100. if (ret < 0) {
  101. SGX_DBG(DBG_E, "Could not normalize path (%s): %s\n", name, pal_strerror(ret));
  102. free(handle);
  103. return NULL;
  104. }
  105. handle->file.realpath = path;
  106. handle->file.total = 0;
  107. handle->file.stubs = NULL;
  108. return handle;
  109. }
  110. static int loader_filter (const char * key, int len)
  111. {
  112. if (len > 7 && key[0] == 'l' && key[1] == 'o' && key[2] == 'a' && key[3] == 'd' &&
  113. key[4] == 'e' && key[5] == 'r' && key[6] == '.')
  114. return 0;
  115. if (len > 4 && key[0] == 's' && key[1] == 'g' && key[2] == 'x' && key[3] == '.')
  116. return 0;
  117. return 1;
  118. }
  119. /*
  120. * Takes a pointer+size to an untrusted memory region containing a
  121. * NUL-separated list of strings. It builds a argv-style list in trusted memory
  122. * with those strings.
  123. *
  124. * It is responsible for handling the access to untrusted memory safely
  125. * (returns NULL on error) and ensures that all strings are properly
  126. * terminated. The content of the strings is NOT further sanitized.
  127. *
  128. * The argv-style list is allocated on the heap and the caller is responsible
  129. * to free it (For argv and envp we rely on auto free on termination in
  130. * practice).
  131. */
  132. static const char** make_argv_list(void * uptr_src, uint64_t src_size) {
  133. const char **argv;
  134. if (src_size == 0) {
  135. argv = malloc(sizeof(char *));
  136. argv[0] = NULL;
  137. return argv;
  138. }
  139. char * data = malloc(src_size);
  140. if (!data) {
  141. return NULL;
  142. }
  143. if (!sgx_copy_to_enclave(data, src_size, uptr_src, src_size)) {
  144. goto free_and_err;
  145. }
  146. data[src_size - 1] = '\0';
  147. uint64_t argc = 0;
  148. for (uint64_t i = 0; i < src_size; i++) {
  149. if (data[i] == '\0') {
  150. argc++;
  151. }
  152. }
  153. size_t argv_size;
  154. if (__builtin_mul_overflow(argc + 1, sizeof(char *), &argv_size)) {
  155. goto free_and_err;
  156. }
  157. argv = malloc(argv_size);
  158. if (!argv) {
  159. goto free_and_err;
  160. }
  161. argv[argc] = NULL;
  162. uint64_t data_i = 0;
  163. for (uint64_t arg_i = 0; arg_i < argc; arg_i++) {
  164. argv[arg_i] = &data[data_i];
  165. while (data[data_i] != '\0') {
  166. data_i++;
  167. }
  168. data_i++;
  169. }
  170. return argv;
  171. free_and_err:
  172. free(data);
  173. return NULL;
  174. }
  175. extern void * enclave_base;
  176. extern void * enclave_top;
  177. void pal_linux_main(char * uptr_args, uint64_t args_size,
  178. char * uptr_env, uint64_t env_size,
  179. struct pal_sec * uptr_sec_info)
  180. {
  181. /*
  182. * Our arguments are comming directly from the urts. We are responsible to
  183. * check them.
  184. */
  185. PAL_HANDLE parent = NULL;
  186. unsigned long start_time = _DkSystemTimeQuery();
  187. int rv;
  188. struct pal_sec sec_info;
  189. if (!sgx_copy_to_enclave(&sec_info, sizeof(sec_info), uptr_sec_info, sizeof(sec_info))) {
  190. return;
  191. }
  192. pal_sec.heap_min = GET_ENCLAVE_TLS(heap_min);
  193. pal_sec.heap_max = GET_ENCLAVE_TLS(heap_max);
  194. pal_sec.exec_addr = GET_ENCLAVE_TLS(exec_addr);
  195. pal_sec.exec_size = GET_ENCLAVE_TLS(exec_size);
  196. /* Zero the heap. We need to take care to not zero the exec area. */
  197. void* zero1_start = pal_sec.heap_min;
  198. void* zero1_end = pal_sec.heap_max;
  199. void* zero2_start = pal_sec.heap_max;
  200. void* zero2_end = pal_sec.heap_max;
  201. if (pal_sec.exec_addr != NULL) {
  202. zero1_end = MIN(zero1_end, SATURATED_P_SUB(pal_sec.exec_addr, MEMORY_GAP, 0));
  203. zero2_start = SATURATED_P_ADD(pal_sec.exec_addr + pal_sec.exec_size, MEMORY_GAP, zero2_end);
  204. }
  205. memset(zero1_start, 0, zero1_end - zero1_start);
  206. memset(zero2_start, 0, zero2_end - zero2_start);
  207. /* relocate PAL itself */
  208. pal_map.l_addr = elf_machine_load_address();
  209. pal_map.l_name = ENCLAVE_PAL_FILENAME;
  210. elf_get_dynamic_info((void *) pal_map.l_addr + elf_machine_dynamic(),
  211. pal_map.l_info, pal_map.l_addr);
  212. ELF_DYNAMIC_RELOCATE(&pal_map);
  213. /*
  214. * We can't verify the following arguments from the urts. So we copy
  215. * them directly but need to be careful when we use them.
  216. */
  217. pal_sec.instance_id = sec_info.instance_id;
  218. COPY_ARRAY(pal_sec.exec_name, sec_info.exec_name);
  219. pal_sec.exec_name[sizeof(pal_sec.exec_name) - 1] = '\0';
  220. COPY_ARRAY(pal_sec.manifest_name, sec_info.manifest_name);
  221. pal_sec.manifest_name[sizeof(pal_sec.manifest_name) - 1] = '\0';
  222. pal_sec.stream_fd = sec_info.stream_fd;
  223. pal_sec.cargo_fd = sec_info.cargo_fd;
  224. COPY_ARRAY(pal_sec.pipe_prefix, sec_info.pipe_prefix);
  225. pal_sec.aesm_targetinfo = sec_info.aesm_targetinfo;
  226. #ifdef DEBUG
  227. pal_sec.in_gdb = sec_info.in_gdb;
  228. #endif
  229. #if PRINT_ENCLAVE_STAT == 1
  230. pal_sec.start_time = sec_info.start_time;
  231. #endif
  232. /* For {p,u,g}ids we can at least do some minimal checking. */
  233. /* ppid should be positive when interpreted as signed. It's 0 if we don't
  234. * have a graphene parent process. */
  235. if (sec_info.ppid > INT32_MAX) {
  236. return;
  237. }
  238. pal_sec.ppid = sec_info.ppid;
  239. /* As ppid but we always have a pid, so 0 is invalid. */
  240. if (sec_info.pid > INT32_MAX || sec_info.pid == 0) {
  241. return;
  242. }
  243. pal_sec.pid = sec_info.pid;
  244. /* -1 is treated as special value for example by chown. */
  245. if (sec_info.uid == (PAL_IDX)-1 || sec_info.gid == (PAL_IDX)-1) {
  246. return;
  247. }
  248. pal_sec.uid = sec_info.uid;
  249. pal_sec.gid = sec_info.gid;
  250. int num_cpus = sec_info.num_cpus;
  251. if (num_cpus >= 1 && num_cpus <= (1 << 16)) {
  252. pal_sec.num_cpus = num_cpus;
  253. } else {
  254. return;
  255. }
  256. /* set up page allocator and slab manager */
  257. init_slab_mgr(g_page_size);
  258. init_untrusted_slab_mgr();
  259. init_enclave_pages();
  260. init_enclave_key();
  261. init_cpuid();
  262. /* now we can add a link map for PAL itself */
  263. setup_pal_map(&pal_map);
  264. /* Set the alignment early */
  265. pal_state.alloc_align = g_page_size;
  266. /* initialize enclave properties */
  267. rv = init_enclave();
  268. if (rv) {
  269. SGX_DBG(DBG_E, "Failed to initialize enclave properties: %d\n", rv);
  270. ocall_exit(rv, /*is_exitgroup=*/true);
  271. }
  272. if (args_size > MAX_ARGS_SIZE || env_size > MAX_ENV_SIZE) {
  273. return;
  274. }
  275. const char ** arguments = make_argv_list(uptr_args, args_size);
  276. if (!arguments) {
  277. return;
  278. }
  279. const char ** environments = make_argv_list(uptr_env, env_size);
  280. if (!environments) {
  281. return;
  282. }
  283. pal_state.start_time = start_time;
  284. linux_state.uid = pal_sec.uid;
  285. linux_state.gid = pal_sec.gid;
  286. linux_state.process_id = (start_time & (~0xffff)) | pal_sec.pid;
  287. SET_ENCLAVE_TLS(ready_for_exceptions, 1UL);
  288. /* if there is a parent, create parent handle */
  289. if (pal_sec.ppid) {
  290. if ((rv = init_child_process(&parent)) < 0) {
  291. SGX_DBG(DBG_E, "Failed to initialize child process: %d\n", rv);
  292. ocall_exit(rv, /*is_exitgroup=*/true);
  293. }
  294. }
  295. /* now let's mark our enclave as initialized */
  296. pal_enclave_state.enclave_flags |= PAL_ENCLAVE_INITIALIZED;
  297. /*
  298. * We create dummy handles for exec and manifest here to make the logic in
  299. * pal_main happy and pass the path of them. The handles can't be used to
  300. * read anything.
  301. */
  302. PAL_HANDLE manifest, exec = NULL;
  303. manifest = setup_dummy_file_handle(pal_sec.manifest_name);
  304. if (pal_sec.exec_name[0] != '\0') {
  305. exec = setup_dummy_file_handle(pal_sec.exec_name);
  306. } else {
  307. SGX_DBG(DBG_I, "Run without executable\n");
  308. }
  309. uint64_t manifest_size = GET_ENCLAVE_TLS(manifest_size);
  310. void* manifest_addr = enclave_top - ALIGN_UP_PTR_POW2(manifest_size, g_page_size);
  311. /* parse manifest data into config storage */
  312. struct config_store * root_config =
  313. malloc(sizeof(struct config_store));
  314. root_config->raw_data = manifest_addr;
  315. root_config->raw_size = manifest_size;
  316. root_config->malloc = malloc;
  317. root_config->free = free;
  318. const char * errstring = NULL;
  319. if ((rv = read_config(root_config, loader_filter, &errstring)) < 0) {
  320. SGX_DBG(DBG_E, "Can't read manifest: %s, error code %d\n", errstring, rv);
  321. ocall_exit(rv, /*is_exitgroup=*/true);
  322. }
  323. pal_state.root_config = root_config;
  324. __pal_control.manifest_preload.start = (PAL_PTR) manifest_addr;
  325. __pal_control.manifest_preload.end = (PAL_PTR) manifest_addr + manifest_size;
  326. if ((rv = init_trusted_platform()) < 0) {
  327. SGX_DBG(DBG_E, "Failed to verify the platform using remote attestation: %d\n", rv);
  328. ocall_exit(rv, true);
  329. }
  330. if ((rv = init_trusted_files()) < 0) {
  331. SGX_DBG(DBG_E, "Failed to load the checksums of trusted files: %d\n", rv);
  332. ocall_exit(rv, true);
  333. }
  334. if ((rv = init_trusted_children()) < 0) {
  335. SGX_DBG(DBG_E, "Failed to load the measurement of trusted child enclaves: %d\n", rv);
  336. ocall_exit(rv, true);
  337. }
  338. if ((rv = init_file_check_policy()) < 0) {
  339. SGX_DBG(DBG_E, "Failed to load the file check policy: %d\n", rv);
  340. ocall_exit(rv, true);
  341. }
  342. #if PRINT_ENCLAVE_STAT == 1
  343. printf(" >>>>>>>> "
  344. "Enclave loading time = %10ld milliseconds\n",
  345. _DkSystemTimeQuery() - pal_sec.start_time);
  346. #endif
  347. /* set up thread handle */
  348. PAL_HANDLE first_thread = malloc(HANDLE_SIZE(thread));
  349. SET_HANDLE_TYPE(first_thread, thread);
  350. first_thread->thread.tcs =
  351. enclave_base + GET_ENCLAVE_TLS(tcs_offset);
  352. __pal_control.first_thread = first_thread;
  353. SET_ENCLAVE_TLS(thread, &first_thread->thread);
  354. /* call main function */
  355. pal_main(pal_sec.instance_id, manifest, exec,
  356. pal_sec.exec_addr, parent, first_thread,
  357. arguments, environments);
  358. }
  359. /* the following code is borrowed from CPUID */
  360. static void cpuid (unsigned int leaf, unsigned int subleaf,
  361. unsigned int words[])
  362. {
  363. _DkCpuIdRetrieve(leaf, subleaf, words);
  364. }
  365. #define FOUR_CHARS_VALUE(s, w) \
  366. (s)[0] = (w) & 0xff; \
  367. (s)[1] = ((w) >> 8) & 0xff; \
  368. (s)[2] = ((w) >> 16) & 0xff; \
  369. (s)[3] = ((w) >> 24) & 0xff;
  370. #define BPI 32
  371. #define POWER2(power) \
  372. (1ULL << (power))
  373. #define RIGHTMASK(width) \
  374. (((unsigned long)(width) >= BPI) ? ~0ULL : POWER2(width) - 1ULL)
  375. #define BIT_EXTRACT_LE(value, start, after) \
  376. (((unsigned long)(value) & RIGHTMASK(after)) >> start)
  377. static char * cpu_flags[]
  378. = { "fpu", // "x87 FPU on chip"
  379. "vme", // "virtual-8086 mode enhancement"
  380. "de", // "debugging extensions"
  381. "pse", // "page size extensions"
  382. "tsc", // "time stamp counter"
  383. "msr", // "RDMSR and WRMSR support"
  384. "pae", // "physical address extensions"
  385. "mce", // "machine check exception"
  386. "cx8", // "CMPXCHG8B inst."
  387. "apic", // "APIC on chip"
  388. NULL,
  389. "sep", // "SYSENTER and SYSEXIT"
  390. "mtrr", // "memory type range registers"
  391. "pge", // "PTE global bit"
  392. "mca", // "machine check architecture"
  393. "cmov", // "conditional move/compare instruction"
  394. "pat", // "page attribute table"
  395. "pse36", // "page size extension"
  396. "pn", // "processor serial number"
  397. "clflush", // "CLFLUSH instruction"
  398. NULL,
  399. "dts", // "debug store"
  400. "acpi", // "Onboard thermal control"
  401. "mmx", // "MMX Technology"
  402. "fxsr", // "FXSAVE/FXRSTOR"
  403. "sse", // "SSE extensions"
  404. "sse2", // "SSE2 extensions"
  405. "ss", // "self snoop"
  406. "ht", // "hyper-threading / multi-core supported"
  407. "tm", // "therm. monitor"
  408. "ia64", // "IA64"
  409. "pbe", // "pending break event"
  410. };
  411. static double get_bogomips(void) {
  412. int fd = -1;
  413. char buf[0x800] = { 0 };
  414. fd = ocall_open("/proc/cpuinfo", O_RDONLY, 0);
  415. if (fd < 0) {
  416. return 0.0;
  417. }
  418. /* Although the whole file might not fit in this size, the first cpu description should. */
  419. int x = ocall_read(fd, buf, sizeof(buf) - 1);
  420. ocall_close(fd);
  421. if (x < 0) {
  422. return 0.0;
  423. }
  424. return sanitize_bogomips_value(get_bogomips_from_cpuinfo_buf(buf, sizeof(buf)));
  425. }
  426. int _DkGetCPUInfo (PAL_CPU_INFO * ci)
  427. {
  428. unsigned int words[PAL_CPUID_WORD_NUM];
  429. int rv = 0;
  430. const size_t VENDOR_ID_SIZE = 13;
  431. char* vendor_id = malloc(VENDOR_ID_SIZE);
  432. cpuid(0, 0, words);
  433. FOUR_CHARS_VALUE(&vendor_id[0], words[PAL_CPUID_WORD_EBX]);
  434. FOUR_CHARS_VALUE(&vendor_id[4], words[PAL_CPUID_WORD_EDX]);
  435. FOUR_CHARS_VALUE(&vendor_id[8], words[PAL_CPUID_WORD_ECX]);
  436. vendor_id[VENDOR_ID_SIZE - 1] = '\0';
  437. ci->cpu_vendor = vendor_id;
  438. // Must be an Intel CPU
  439. if (memcmp(vendor_id, "GenuineIntel", 12)) {
  440. free(vendor_id);
  441. return -PAL_ERROR_INVAL;
  442. }
  443. const size_t BRAND_SIZE = 49;
  444. char* brand = malloc(BRAND_SIZE);
  445. cpuid(0x80000002, 0, words);
  446. memcpy(&brand[ 0], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
  447. cpuid(0x80000003, 0, words);
  448. memcpy(&brand[16], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
  449. cpuid(0x80000004, 0, words);
  450. memcpy(&brand[32], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
  451. brand[BRAND_SIZE - 1] = '\0';
  452. ci->cpu_brand = brand;
  453. /* we cannot use CPUID(0xb) because it counts even disabled-by-BIOS cores (e.g. HT cores);
  454. * instead, this is passed in via pal_sec at start-up time. */
  455. ci->cpu_num = pal_sec.num_cpus;
  456. cpuid(1, 0, words);
  457. ci->cpu_family = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX], 8, 12) +
  458. BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX], 20, 28);
  459. ci->cpu_model = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX], 4, 8) +
  460. (BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX], 16, 20) << 4);
  461. ci->cpu_stepping = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX], 0, 4);
  462. int flen = 0, fmax = 80;
  463. char * flags = malloc(fmax);
  464. for (int i = 0 ; i < 32 ; i++) {
  465. if (!cpu_flags[i])
  466. continue;
  467. if (BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EDX], i, i + 1)) {
  468. int len = strlen(cpu_flags[i]);
  469. if (flen + len + 1 > fmax) {
  470. char * new_flags = malloc(fmax * 2);
  471. memcpy(new_flags, flags, flen);
  472. free(flags);
  473. fmax *= 2;
  474. flags = new_flags;
  475. }
  476. memcpy(flags + flen, cpu_flags[i], len);
  477. flen += len;
  478. flags[flen++] = ' ';
  479. }
  480. }
  481. flags[flen ? flen - 1 : 0] = 0;
  482. ci->cpu_flags = flags;
  483. ci->cpu_bogomips = get_bogomips();
  484. if (ci->cpu_bogomips == 0.0) {
  485. SGX_DBG(DBG_E, "Warning: bogomips could not be retrieved, passing 0.0 to the application\n");
  486. }
  487. return rv;
  488. }