/* SPDX-License-Identifier: GPL-2.0-only */ /* * Guest Ultravisor Call tests * * Copyright IBM Corp. 2020, 2022 * * Authors: * Janosch Frank */ #include #include #include #include #include #include #include #include #include static unsigned long page; static void test_priv(void) { struct uv_cb_header uvcb = {}; report_prefix_push("privileged"); report_prefix_push("query"); uvcb.cmd = UVC_CMD_QUI; uvcb.len = sizeof(struct uv_cb_qui); expect_pgm_int(); enter_pstate(); uv_call_once(0, (u64)&uvcb); check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION); report_prefix_pop(); report_prefix_push("share"); uvcb.cmd = UVC_CMD_SET_SHARED_ACCESS; uvcb.len = sizeof(struct uv_cb_share); expect_pgm_int(); enter_pstate(); uv_call_once(0, (u64)&uvcb); check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION); report_prefix_pop(); report_prefix_push("unshare"); uvcb.cmd = UVC_CMD_REMOVE_SHARED_ACCESS; uvcb.len = sizeof(struct uv_cb_share); expect_pgm_int(); enter_pstate(); uv_call_once(0, (u64)&uvcb); check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION); report_prefix_pop(); report_prefix_push("attest"); uvcb.cmd = UVC_CMD_ATTESTATION; uvcb.len = sizeof(struct uv_cb_attest); expect_pgm_int(); enter_pstate(); uv_call_once(0, (uint64_t)&uvcb); check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION); report_prefix_pop(); report_prefix_pop(); } static void test_query(void) { struct uv_cb_qui uvcb = { .header.cmd = UVC_CMD_QUI, /* A dword below the minimum length */ .header.len = 0xa0, }; int cc; report_prefix_push("query"); cc = uv_call(0, (u64)&uvcb); report(cc == 1 && uvcb.header.rc == UVC_RC_INV_LEN, "length"); /* * BIT_UVC_CMD_QUI, BIT_UVC_CMD_SET_SHARED_ACCESS and * BIT_UVC_CMD_REMOVE_SHARED_ACCESS are always present as they * have been introduced with the first Ultravisor version. * However, we only need to check for QUI as * SET/REMOVE SHARED are used to fence this test to be only * executed by protected guests. */ report(uv_query_test_call(BIT_UVC_CMD_QUI), "query indicated"); report_prefix_pop(); } static void test_sharing(void) { struct uv_cb_share uvcb = { .header.cmd = UVC_CMD_SET_SHARED_ACCESS, .header.len = sizeof(uvcb) - 8, .paddr = page, }; int cc; report_prefix_push("share"); cc = uv_call(0, (u64)&uvcb); report(cc == 1 && uvcb.header.rc == UVC_RC_INV_LEN, "length"); uvcb.header.len = sizeof(uvcb); cc = uv_call(0, (u64)&uvcb); report(cc == 0 && uvcb.header.rc == UVC_RC_EXECUTED, "share"); uvcb.paddr = get_ram_size() + PAGE_SIZE; cc = uv_call(0, (u64)&uvcb); report(cc == 1 && uvcb.header.rc == 0x101, "invalid memory"); uvcb.paddr = page; report_prefix_pop(); report_prefix_push("unshare"); uvcb.header.cmd = UVC_CMD_REMOVE_SHARED_ACCESS; uvcb.header.len -= 8; cc = uv_call(0, (u64)&uvcb); report(cc == 1 && uvcb.header.rc == UVC_RC_INV_LEN, "length"); uvcb.header.len = sizeof(uvcb); cc = uv_call(0, (u64)&uvcb); report(cc == 0 && uvcb.header.rc == UVC_RC_EXECUTED, "unshare"); report_prefix_pop(); } static struct { const char *name; uint16_t cmd; uint16_t len; int call_bit; } invalid_cmds[] = { { "bogus", 0x4242, sizeof(struct uv_cb_header), -1 }, { "init", UVC_CMD_INIT_UV, sizeof(struct uv_cb_init), BIT_UVC_CMD_INIT_UV }, { "create conf", UVC_CMD_CREATE_SEC_CONF, sizeof(struct uv_cb_cgc), BIT_UVC_CMD_CREATE_SEC_CONF }, { "destroy conf", UVC_CMD_DESTROY_SEC_CONF, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_DESTROY_SEC_CONF }, { "create cpu", UVC_CMD_CREATE_SEC_CPU, sizeof(struct uv_cb_csc), BIT_UVC_CMD_CREATE_SEC_CPU }, { "destroy cpu", UVC_CMD_DESTROY_SEC_CPU, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_DESTROY_SEC_CPU }, { "conv to", UVC_CMD_CONV_TO_SEC_STOR, sizeof(struct uv_cb_cts), BIT_UVC_CMD_CONV_TO_SEC_STOR }, { "conv from", UVC_CMD_CONV_FROM_SEC_STOR, sizeof(struct uv_cb_cfs), BIT_UVC_CMD_CONV_FROM_SEC_STOR }, { "set sec conf", UVC_CMD_SET_SEC_CONF_PARAMS, sizeof(struct uv_cb_ssc), BIT_UVC_CMD_SET_SEC_PARMS }, { "unpack", UVC_CMD_UNPACK_IMG, sizeof(struct uv_cb_unp), BIT_UVC_CMD_UNPACK_IMG }, { "verify", UVC_CMD_VERIFY_IMG, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_VERIFY_IMG }, { "cpu reset", UVC_CMD_CPU_RESET, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_RESET }, { "cpu initial reset", UVC_CMD_CPU_RESET_INITIAL, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_RESET_INITIAL }, { "prepare clear reset", UVC_CMD_PREPARE_RESET, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_PREPARE_RESET }, { "cpu clear reset", UVC_CMD_CPU_RESET_CLEAR, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_PERFORM_CLEAR_RESET }, { "cpu set state", UVC_CMD_CPU_SET_STATE, sizeof(struct uv_cb_cpu_set_state), BIT_UVC_CMD_CPU_SET_STATE }, { "pin shared", UVC_CMD_PIN_PAGE_SHARED, sizeof(struct uv_cb_cfs), BIT_UVC_CMD_PIN_PAGE_SHARED }, { "unpin shared", UVC_CMD_UNPIN_PAGE_SHARED, sizeof(struct uv_cb_cts), BIT_UVC_CMD_UNPIN_PAGE_SHARED }, { NULL, 0, 0 }, }; static void test_invalid(void) { struct uv_cb_header *hdr = (void *)page; int cc, i; report_prefix_push("invalid"); for (i = 0; invalid_cmds[i].name; i++) { hdr->cmd = invalid_cmds[i].cmd; hdr->len = invalid_cmds[i].len; cc = uv_call(0, (u64)hdr); report(cc == 1 && hdr->rc == UVC_RC_INV_CMD && (invalid_cmds[i].call_bit == -1 || !uv_query_test_call(invalid_cmds[i].call_bit)), "%s", invalid_cmds[i].name); } report_prefix_pop(); } static void test_share_bits(void) { bool unshare = uv_query_test_call(BIT_UVC_CMD_REMOVE_SHARED_ACCESS); bool share = uv_query_test_call(BIT_UVC_CMD_SET_SHARED_ACCESS); report_prefix_push("query"); report(!(share ^ unshare), "share bits are identical"); report_prefix_pop(); } int main(void) { bool has_uvc = test_facility(158); report_prefix_push("uvc"); if (!has_uvc) { report_skip("Ultravisor call facility is not available"); goto done; } /* * Needs to be done before the guest-fence, * as the fence tests if both shared bits are present */ test_share_bits(); if (!uv_os_is_guest()) { report_skip("Not a protected guest"); goto done; } page = (unsigned long)alloc_page(); test_priv(); test_invalid(); test_query(); test_sharing(); free_page((void *)page); done: report_prefix_pop(); return report_summary(); }