diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc01395..afff72b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,11 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, ubuntu-20.04] + os: [ubuntu-20.04, ubuntu-22.04] steps: + - name: Install required packages + run: sudo apt-get install uuid-dev - uses: actions/checkout@v1 - name: Bootstrap run: ./bootstrap diff --git a/README.md b/README.md index 72b8f36..b3b6a70 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Build](https://github.com/bitdefender/libkvmi/workflows/Build/badge.svg) -(c) 2017-2021 Bitdefender SRL +(c) 2017-2023 Bitdefender SRL ## Usage diff --git a/configure.ac b/configure.ac index 5cdba68..41798b5 100644 --- a/configure.ac +++ b/configure.ac @@ -28,4 +28,6 @@ AS_CASE([$host_cpu], ) AC_SUBST([ARCH]) +AC_CHECK_HEADERS([uuid/uuid.h], [], [AC_MSG_ERROR([please install the uuid development package])]) + AC_OUTPUT(Makefile src/Makefile include/Makefile examples/Makefile libkvmi.pc) diff --git a/include/libkvmi.h b/include/libkvmi.h index 112d197..e394585 100644 --- a/include/libkvmi.h +++ b/include/libkvmi.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Bitdefender S.R.L. + * Copyright (C) 2017-2023 Bitdefender S.R.L. * * The KVMI Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/include/linux/kvmi.h b/include/linux/kvmi.h index af3a62d..f6a3efc 100644 --- a/include/linux/kvmi.h +++ b/include/linux/kvmi.h @@ -13,47 +13,47 @@ #define KVMI_VERSION 0x00000001 enum { - KVMI_EVENT_REPLY = 0, - KVMI_EVENT = 1, - - KVMI_GET_VERSION = 2, - KVMI_CHECK_COMMAND = 3, - KVMI_CHECK_EVENT = 4, - KVMI_GET_GUEST_INFO = 5, - KVMI_GET_VCPU_INFO = 6, - KVMI_PAUSE_VCPU = 7, - KVMI_CONTROL_VM_EVENTS = 8, - KVMI_CONTROL_EVENTS = 9, - KVMI_CONTROL_CR = 10, - KVMI_CONTROL_MSR = 11, - KVMI_CONTROL_VE = 12, - KVMI_GET_REGISTERS = 13, - KVMI_SET_REGISTERS = 14, - KVMI_GET_CPUID = 15, - KVMI_GET_XSAVE = 16, - KVMI_READ_PHYSICAL = 17, - KVMI_WRITE_PHYSICAL = 18, - KVMI_INJECT_EXCEPTION = 19, - KVMI_GET_PAGE_ACCESS = 20, - KVMI_SET_PAGE_ACCESS = 21, - KVMI_GET_MAP_TOKEN = 22, - KVMI_GET_MTRR_TYPE = 23, - KVMI_CONTROL_SPP = 24, - KVMI_GET_PAGE_WRITE_BITMAP = 25, - KVMI_SET_PAGE_WRITE_BITMAP = 26, - KVMI_CONTROL_CMD_RESPONSE = 27, - KVMI_SET_VE_INFO_PAGE = 28, - KVMI_GET_MAX_GFN = 29, - KVMI_SET_EPT_PAGE_CONV = 30, - KVMI_GET_EPT_PAGE_CONV = 31, - KVMI_SWITCH_EPT_VIEW = 32, - KVMI_DISABLE_VE = 33, - KVMI_GET_EPT_VIEW = 34, - KVMI_VCPU_TRANSLATE_GVA = 35, - KVMI_CONTROL_EPT_VIEW = 36, - KVMI_VCPU_GET_XCR = 37, - KVMI_VCPU_SET_XSAVE = 38, - KVMI_VCPU_CHANGE_GFN = 60, + KVMI_EVENT_REPLY = 0, + KVMI_EVENT = 1, + + KVMI_GET_VERSION = 2, + KVMI_CHECK_COMMAND = 3, + KVMI_CHECK_EVENT = 4, + KVMI_GET_GUEST_INFO = 5, + KVMI_GET_VCPU_INFO = 6, + KVMI_PAUSE_VCPU = 7, + KVMI_CONTROL_VM_EVENTS = 8, + KVMI_CONTROL_EVENTS = 9, + KVMI_CONTROL_CR = 10, + KVMI_CONTROL_MSR = 11, + KVMI_CONTROL_VE = 12, + KVMI_GET_REGISTERS = 13, + KVMI_SET_REGISTERS = 14, + KVMI_GET_CPUID = 15, + KVMI_GET_XSAVE = 16, + KVMI_READ_PHYSICAL = 17, + KVMI_WRITE_PHYSICAL = 18, + KVMI_INJECT_EXCEPTION = 19, + KVMI_GET_PAGE_ACCESS = 20, + KVMI_SET_PAGE_ACCESS = 21, + KVMI_GET_MAP_TOKEN = 22, + KVMI_GET_MTRR_TYPE = 23, + KVMI_CONTROL_SPP = 24, + KVMI_GET_PAGE_WRITE_BITMAP = 25, + KVMI_SET_PAGE_WRITE_BITMAP = 26, + KVMI_CONTROL_CMD_RESPONSE = 27, + KVMI_SET_VE_INFO_PAGE = 28, + KVMI_GET_MAX_GFN = 29, + KVMI_GET_NEXT_AVAILABLE_GFN = 31, + KVMI_SWITCH_EPT_VIEW = 32, + KVMI_DISABLE_VE = 33, + KVMI_GET_EPT_VIEW = 34, + KVMI_VCPU_TRANSLATE_GVA = 35, + KVMI_CONTROL_EPT_VIEW = 36, + KVMI_VCPU_GET_XCR = 37, + KVMI_VCPU_SET_XSAVE = 38, + KVMI_QUERY_PHYSICAL = 39, + KVMI_VCPU_CHANGE_GFN = 60, KVMI_VCPU_CONTROL_SINGLESTEP = 63, @@ -201,6 +201,15 @@ struct kvmi_write_physical { __u8 data[0]; }; +struct kvmi_query_physical { + __u64 gfn; +}; + +struct kvmi_query_physical_reply { + __u64 gfn; + __u64 size; +}; + struct kvmi_vcpu_hdr { __u16 vcpu; __u16 padding1; @@ -262,6 +271,10 @@ struct kvmi_get_max_gfn_reply { __u64 gfn; }; +struct kvmi_get_next_available_gfn_reply { + __u64 gfn; +}; + struct kvmi_set_ve_info_page { __u64 gpa; __u8 trigger_vmexit; diff --git a/src/Makefile.am b/src/Makefile.am index 1e61158..39d4cbe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,5 +3,5 @@ AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/include/linux/$(ARCH) lib_LTLIBRARIES = libkvmi.la libkvmi_la_SOURCES = kvmi.c -libkvmi_la_LDFLAGS = -pthread -version-number 1:1 \ +libkvmi_la_LDFLAGS = -luuid -pthread -version-number 1:1 \ -Wl,--version-script,$(srcdir)/version.ld diff --git a/src/kvmi.c b/src/kvmi.c index 598c013..147b070 100644 --- a/src/kvmi.c +++ b/src/kvmi.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Bitdefender S.R.L. + * Copyright (C) 2017-2023 Bitdefender S.R.L. * * The KVMI Library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,6 +37,7 @@ #include #include #include +#include #include "libkvmi.h" #include "kvm_compat.h" @@ -101,6 +102,7 @@ struct kvmi_dom { struct kvmi_features supported; bool disconnected; int mem_fd; + bool mem_remote; list_t mem_cache; pthread_mutex_t mem_lock; void * cb_ctx; @@ -170,6 +172,7 @@ struct kvmi_pause_vcpu_msg { }; static long pagesize; +static short pageshift; static size_t batch_preallocated_size; static kvmi_log_cb log_cb; static void * log_ctx; @@ -190,6 +193,7 @@ __attribute__( ( constructor ) ) static void lib_init( void ) int fd; pagesize = sysconf( _SC_PAGE_SIZE ); + pageshift = __builtin_ctzl( pagesize ); batch_preallocated_size = pagesize * BATCH_PREALLOCATED_PAGES; fd = open( "/dev/kvmmem", O_RDWR ); @@ -305,9 +309,12 @@ bool kvmi_domain_is_connected( const void *d ) static int kvmi_open_kvmmem( struct kvmi_dom *dom ) { + char proc_entry_name[52] = "/proc/kvmi-mem-"; + if ( dom->mem_fd != -1 ) return 0; + dom->mem_remote = true; dom->mem_fd = open( "/dev/kvmmem", O_RDWR ); if ( dom->mem_fd != -1 ) { if ( !mem_v2 ) @@ -323,6 +330,12 @@ static int kvmi_open_kvmmem( struct kvmi_dom *dom ) } } + if ( dom->mem_fd == -1 ) { + uuid_unparse_upper( dom->hsk.uuid, proc_entry_name + 15 ); + dom->mem_remote = false; + dom->mem_fd = open( proc_entry_name, O_RDWR ); + } + return dom->mem_fd < 0 ? -1 : 0; } @@ -504,9 +517,9 @@ static int do_write( struct kvmi_dom *dom, struct iovec *iov, size_t iov_len, si while ( to_send ) { ssize_t n; - if ( !prev ) { + if ( !prev ) n = do_write_iov( dom, iov + iov_idx, iov_len - iov_idx ); - } else { + else { struct iovec tmp; tmp.iov_base = iov[iov_idx].iov_base + prev; @@ -1606,7 +1619,6 @@ int kvmi_pause_all_vcpus( void *dom, unsigned int count ) return -1; for ( vcpu = 0; vcpu < count; vcpu++ ) { - setup_kvmi_pause_vcpu_msg( &msg, vcpu ); msg.cmd.wait = 1; @@ -2117,13 +2129,114 @@ static int kvmi_unmap_physical_page_v1( void *d, void *addr ) return err; } +static void *kvmi_map_physical_page_host( void *d, unsigned long long int gpa ) +{ + struct kvmi_dom *dom = d; + + struct kvmi_mem_region *region; + struct kvmi_query_physical req; + struct kvmi_query_physical_reply rpl; + size_t received = sizeof( rpl ); + int err; + + pthread_mutex_lock( &dom->mem_lock ); + + /* first look-up physical address in region cache */ + region = kvmi_mem_cache_lookup_gpa( dom, gpa ); + if ( region ) { + region->refcount++; + goto out; + } + + region = malloc( sizeof( *region ) ); + if ( !region ) { + pthread_mutex_unlock( &dom->mem_lock ); + return MAP_FAILED; + } + + /* fill request */ + req.gfn = gpa >> pageshift; + + /* request mem slot */ + err = request( dom, KVMI_QUERY_PHYSICAL, &req, sizeof( req ), &rpl, &received ); + + if ( err ) { + free( region ); + pthread_mutex_unlock( &dom->mem_lock ); + return MAP_FAILED; + } + + region->start = rpl.gfn << pageshift; + region->length = rpl.size * pagesize; + region->virt = mmap( NULL, region->length, PROT_READ | PROT_WRITE, MAP_SHARED, dom->mem_fd, region->start ); + if ( region->virt == MAP_FAILED ) { + free( region ); + pthread_mutex_unlock( &dom->mem_lock ); + return MAP_FAILED; + } + + /* add region to cache */ + region->refcount = 1; + list_add_tail( &dom->mem_cache, ®ion->link ); + +out: + pthread_mutex_unlock( &dom->mem_lock ); + + return ( char * )region->virt + ( gpa - region->start ); +} + +static int kvmi_unmap_physical_page_host( void *d, void *addr ) +{ + struct kvmi_dom * dom = d; + struct kvmi_mem_region *reg; + int err = 0; + + /* validate input address */ + if ( addr == NULL ) { + errno = EINVAL; + return -1; + } + + pthread_mutex_lock( &dom->mem_lock ); + + /* look-up region by local virtual address */ + reg = kvmi_mem_cache_lookup_virt( dom, addr ); + if ( !reg ) { + pthread_mutex_unlock( &dom->mem_lock ); + errno = ENXIO; + return -1; + } + + /* dec region reference count & unmap region if unreferenced */ + reg->refcount--; + if ( reg->refcount == 0 ) { + munmap( reg->virt, reg->length ); + list_del( ®->link ); + free( reg ); + } + + pthread_mutex_unlock( &dom->mem_lock ); + + return err; +} + void *kvmi_map_physical_page( void *d, unsigned long long int gpa ) { + struct kvmi_dom *dom = d; + + if ( !dom->mem_remote && dom->mem_fd >= 0 ) + return kvmi_map_physical_page_host( d, gpa ); + return mem_v2 ? kvmi_map_physical_page_v2( d, gpa ) : kvmi_map_physical_page_v1( d, gpa ); } int kvmi_unmap_physical_page( void *d, void *addr ) { + struct kvmi_dom *dom = d; + + if ( !dom->mem_remote && dom->mem_fd >= 0 ) + return kvmi_unmap_physical_page_host( d, addr ); + return mem_v2 ? kvmi_unmap_physical_page_v2( d, addr ) : kvmi_unmap_physical_page_v1( d, addr ); } @@ -2457,6 +2570,19 @@ int kvmi_get_maximum_gfn( void *dom, unsigned long long *gfn ) return err; } +int kvmi_get_next_available_gfn( void *dom, unsigned long long *gfn ) +{ + struct kvmi_get_next_available_gfn_reply rpl; + size_t received = sizeof( rpl ); + int err; + + err = request( dom, KVMI_GET_NEXT_AVAILABLE_GFN, NULL, 0, &rpl, &received ); + if ( !err ) + *gfn = rpl.gfn; + + return err; +} + /* begin of VE related functions */ int kvmi_set_ve_info_page( void *dom, unsigned short vcpu, unsigned long long int gpa ) { @@ -2468,27 +2594,6 @@ int kvmi_set_ve_info_page( void *dom, unsigned short vcpu, unsigned long long in return request( dom, KVMI_SET_VE_INFO_PAGE, &req, sizeof( req ), NULL, 0 ); } -int kvmi_set_ept_page_conv( void *dom, unsigned short index, unsigned long long gpa, bool sve ) -{ - struct kvmi_set_ept_page_conv_req req = { .view = index, .gpa = gpa, .sve = sve }; - - return request( dom, KVMI_SET_EPT_PAGE_CONV, &req, sizeof( req ), NULL, 0 ); -} - -int kvmi_get_ept_page_conv( void *dom, unsigned short index, unsigned long long gpa, bool *sve ) -{ - struct kvmi_get_ept_page_conv_req req = { .view = index, .gpa = gpa }; - struct kvmi_get_ept_page_conv_reply rpl; - int err; - size_t received = sizeof( rpl ); - - err = request( dom, KVMI_GET_EPT_PAGE_CONV, &req, sizeof( req ), &rpl, &received ); - if ( !err ) - *sve = !!rpl.sve; - - return err; -} - int kvmi_switch_ept_view( void *dom, unsigned short vcpu, unsigned short view ) { struct { diff --git a/src/version.ld b/src/version.ld index fe0bf0e..d08ef52 100644 --- a/src/version.ld +++ b/src/version.ld @@ -19,6 +19,7 @@ KVMI_1.0 { kvmi_eptp_support; kvmi_get_cpuid; kvmi_get_maximum_gfn; + kvmi_get_next_available_gfn; kvmi_get_pending_events; kvmi_get_registers; kvmi_get_starttime;