diff --git a/include/memmodel.h b/include/memmodel.h index 74db7f1..983efa8 100644 --- a/include/memmodel.h +++ b/include/memmodel.h @@ -62,7 +62,47 @@ vmie_mem* vmie_mem_open(const char* path, uint64_t low); * Returns a heap-owned handle, or NULL on failure. Release with vmie_mem_close(). */ vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg); -/* Unmap, close, and free a handle from vmie_mem_open*. Safe on NULL. */ +/* Build a source from an already-open file descriptor `fd` instead of a path, + * with the single-`low` map (as vmie_mem_open). `fd` is BORROWED: it is dup()'d + * internally, so the source owns its own copy and the caller's `fd` stays open + * and valid both after this call AND after vmie_mem_close(). `fd` must reference + * an mmap-able, read-write object (a regular file, memfd_create, or POSIX shm) - + * a pipe/socket/tty is not mmap-able and yields NULL. The fd's file position is + * not read or changed (the mapping starts at offset 0). Returns a heap-owned + * handle, or NULL on dup/fstat/mmap failure. Release with vmie_mem_close(). */ +vmie_mem* vmie_mem_from_fd(int fd, uint64_t low); + +/* As vmie_mem_from_fd, but with an explicit segment map (`nseg` entries; see + * gpa_seg), well-formed against the file size. Same dup-borrow semantics: the + * caller's `fd` stays valid after this call and after vmie_mem_close(); `fd` + * must be an mmap-able RW object and its position is untouched. Returns a + * heap-owned handle, or NULL on failure. Release with vmie_mem_close(). */ +vmie_mem* vmie_mem_from_fd_segs(int fd, const gpa_seg* segs, int nseg); + +/* Read-only twin of vmie_mem_from_fd: build a source from `fd` with the + * single-`low` map, but map it PROT_READ and mark it read-only. On the returned + * handle gpa_write/gva_write (and any write through the engine) return -1; all + * reads - gpa_read/gva_read, gpa_ptr/gva_ptr, gva_regions, sig_scan_mem - behave + * exactly as on an RW source. Because only read access is needed, `fd` may be + * opened O_RDONLY (an O_RDWR fd is also accepted). Same dup-borrow semantics: the + * fd is dup()'d internally, so the caller's `fd` stays valid after this call AND + * after vmie_mem_close(); its file position is not read or changed. NOTE: writing + * THROUGH a pointer returned by gpa_ptr/gva_ptr on a read-only source is a + * contract violation (the pages are read-only and the store will fault) - use + * such pointers for reading only. Returns a heap-owned handle, or NULL on + * dup/fstat/mmap failure. Release with vmie_mem_close(). */ +vmie_mem* vmie_mem_from_ro_fd(int fd, uint64_t low); + +/* As vmie_mem_from_ro_fd, but with an explicit segment map (`nseg` entries; see + * gpa_seg), well-formed against the file size. Same read-only contract + * (writes -> -1; no writes through gpa_ptr/gva_ptr) and dup-borrow semantics + * (caller's `fd` stays valid after this call and after vmie_mem_close(); accepts + * an O_RDONLY or O_RDWR fd; position untouched). Returns a heap-owned handle, or + * NULL on failure. Release with vmie_mem_close(). */ +vmie_mem* vmie_mem_from_ro_fd_segs(int fd, const gpa_seg* segs, int nseg); + +/* Unmap, close, and free a handle from vmie_mem_open*, vmie_mem_from_fd*, or + * vmie_mem_from_ro_fd*. Safe on NULL. */ void vmie_mem_close(vmie_mem* m); /* ---- flat memory view (single owner) ------------------------------------- * diff --git a/include/win32.h b/include/win32.h index 4d38266..b714e93 100644 --- a/include/win32.h +++ b/include/win32.h @@ -83,6 +83,15 @@ typedef struct { * failure. Free with vmie_win32_close(). */ vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low); +/* As vmie_win32_open, but over an already-open file descriptor `fd` for the RAM + * backing file instead of a path. `fd` is BORROWED: it is dup()'d internally, so + * the context owns its own copy and the caller's `fd` stays open and valid after + * this call AND after vmie_win32_close(). `fd` must reference an mmap-able, + * read-write object (regular file / memfd_create / POSIX shm); its file position + * is not read or changed. Returns a new context (call host_bootstrap() next), or + * NULL on dup/fstat/mmap failure. Free with vmie_win32_close(). */ +vmie_win32* vmie_win32_open_fd(int fd, uint64_t low); + /* Unmap, close, and free a context. Safe on NULL. After this, every pointer * into guest memory obtained through this context is invalid. */ void vmie_win32_close(vmie_win32* v); diff --git a/src/core/gpa.c b/src/core/gpa.c index 0e3c823..31f2934 100644 --- a/src/core/gpa.c +++ b/src/core/gpa.c @@ -52,6 +52,9 @@ int gpa_read(vmie_mem* m, uintptr_t offs, void* buf, const size_t nmemb) { } int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, const size_t nmemb) { + if (m->ro) { + return -1; + } if (out_of_bounds(m, &offs, nmemb)) { return -1; } @@ -88,37 +91,64 @@ static int segs_valid(const gpa_seg* segs, int nseg, uint64_t fsize) { return 1; } +/* Finish a vmie_mem over an ALREADY-OWNED fd (from open() or dup()): fstat it, + * validate the seg map against its real size, mmap it shared (PROT_READ when + * `ro`, else PROT_RW), and fill `m`. On ANY failure close `fd` and clean `m` + * (no partial state); on success store the fd in `m->fd`, record `ro`, and + * return 0. The fd's lifetime is now owned by `m` (closed in gpa_close). The + * single mmap/validate site shared by every constructor. */ __attribute__((cold)) -int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg) { +static int gpa_map_owned_fd(vmie_mem* m, int fd, const gpa_seg* segs, int nseg, int ro) { struct stat st; - if ((m->fd = open(path, O_RDWR)) < 0) { - goto ret_; - } - - if (fstat(m->fd, &st) || !segs_valid(segs, nseg, (uint64_t)st.st_size)) { + if (fstat(fd, &st) || !segs_valid(segs, nseg, (uint64_t)st.st_size)) { goto close_; } - if ((m->pa = mmap(NULL, st.st_size, PROT_RW, MAP_SHARED, m->fd, 0)) == MAP_FAILED) { + const int prot = ro ? PROT_READ : PROT_RW; + if ((m->pa = mmap(NULL, st.st_size, prot, MAP_SHARED, fd, 0)) == MAP_FAILED) { close_: - close(m->fd); - ret_: + close(fd); clean_ctx(m); return -1; } + m->fd = fd; m->fsize = st.st_size; m->nseg = nseg; + m->ro = ro; memcpy(m->seg, segs, (size_t)nseg * sizeof *segs); return 0; } -/* Convenience: the classic single-`low` QEMU map. Below the 4 GiB PCI hole the - * file maps 1:1 ([0,low)->file[0,low)); at and above 4 GiB it resumes at file - * offset low. When low >= fsize the hole is never reached, so one inert identity - * seg covering the whole image suffices. */ +/* Build the classic single-`low` QEMU map into out[0..nseg) and return nseg. + * Below the 4 GiB PCI hole the file maps 1:1 ([0,low)->file[0,low)); at and + * above 4 GiB it resumes at file offset low. When low >= fsize the hole is never + * reached, so one inert identity seg covering the whole image suffices (nseg=1). + * The single low-map site shared by gpa_open (fsize from stat) and gpa_from_fd + * (fsize from fstat). */ +__attribute__((cold)) +static int low_segs(uint64_t fsize, uint64_t low, gpa_seg out[2]) { + if (low >= fsize) { + out[0] = (gpa_seg){ 0, fsize, 0 }; + return 1; + } + out[0] = (gpa_seg){ 0, low, 0 }; + out[1] = (gpa_seg){ RAM_H, fsize - low, low }; + return 2; +} + +__attribute__((cold)) +int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg) { + const int fd = open(path, O_RDWR); + if (fd < 0) { + clean_ctx(m); + return -1; + } + return gpa_map_owned_fd(m, fd, segs, nseg, 0); +} + __attribute__((cold)) int gpa_open(vmie_mem* m, const char* path, uintptr_t low) { struct stat st; @@ -126,16 +156,70 @@ int gpa_open(vmie_mem* m, const char* path, uintptr_t low) { clean_ctx(m); return -1; } - const uint64_t fsize = (uint64_t)st.st_size; - const gpa_seg one[1] = { { 0, fsize, 0 } }; - const gpa_seg two[2] = { { 0, low, 0 }, { RAM_H, fsize > low ? fsize - low : 0, low } }; - const gpa_seg* segs = low >= fsize ? one : two; - const int nseg = low >= fsize ? 1 : 2; + gpa_seg segs[2]; + const int nseg = low_segs((uint64_t)st.st_size, low, segs); return gpa_open_segs(m, path, segs, nseg); } +/* fd-backed constructors: build a vmie_mem from an already-open file descriptor + * instead of a path. The fd is BORROWED via dup() - core owns the copy (closed + * in gpa_close); the caller's fd stays valid. The fd must reference an mmap-able + * RW object (regular file / memfd / shm); its file position is not used. */ +__attribute__((cold)) +int gpa_from_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg) { + const int dupfd = dup(fd); + if (dupfd < 0) { + clean_ctx(m); + return -1; + } + return gpa_map_owned_fd(m, dupfd, segs, nseg, 0); +} + +__attribute__((cold)) +int gpa_from_fd(vmie_mem* m, int fd, uintptr_t low) { + struct stat st; + if (fstat(fd, &st)) { + clean_ctx(m); + return -1; + } + + gpa_seg segs[2]; + const int nseg = low_segs((uint64_t)st.st_size, low, segs); + + return gpa_from_fd_segs(m, fd, segs, nseg); +} + +/* read-only twins of gpa_from_fd*: map the borrowed fd PROT_READ and mark the + * source read-only (m->ro), so gpa_write/gva_write return -1. Same dup-borrow + * semantics (core owns the copy, caller's fd stays valid). Accept O_RDONLY as + * well as O_RDWR fds - only read access is required for a PROT_READ mapping. + * Reads/gpa_ptr/scan behave exactly as on an RW source. */ +__attribute__((cold)) +int gpa_from_ro_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg) { + const int dupfd = dup(fd); + if (dupfd < 0) { + clean_ctx(m); + return -1; + } + return gpa_map_owned_fd(m, dupfd, segs, nseg, 1); +} + +__attribute__((cold)) +int gpa_from_ro_fd(vmie_mem* m, int fd, uintptr_t low) { + struct stat st; + if (fstat(fd, &st)) { + clean_ctx(m); + return -1; + } + + gpa_seg segs[2]; + const int nseg = low_segs((uint64_t)st.st_size, low, segs); + + return gpa_from_ro_fd_segs(m, fd, segs, nseg); +} + __attribute__((cold)) void gpa_close(vmie_mem* m) { if (m->pa) { @@ -180,6 +264,58 @@ vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg) { return m; } +__attribute__((cold)) +vmie_mem* vmie_mem_from_fd(int fd, uint64_t low) { + vmie_mem* m = calloc(1, sizeof *m); + if (!m) { + return NULL; + } + if (gpa_from_fd(m, fd, (uintptr_t)low)) { + free(m); + return NULL; + } + return m; +} + +__attribute__((cold)) +vmie_mem* vmie_mem_from_fd_segs(int fd, const gpa_seg* segs, int nseg) { + vmie_mem* m = calloc(1, sizeof *m); + if (!m) { + return NULL; + } + if (gpa_from_fd_segs(m, fd, segs, nseg)) { + free(m); + return NULL; + } + return m; +} + +__attribute__((cold)) +vmie_mem* vmie_mem_from_ro_fd(int fd, uint64_t low) { + vmie_mem* m = calloc(1, sizeof *m); + if (!m) { + return NULL; + } + if (gpa_from_ro_fd(m, fd, (uintptr_t)low)) { + free(m); + return NULL; + } + return m; +} + +__attribute__((cold)) +vmie_mem* vmie_mem_from_ro_fd_segs(int fd, const gpa_seg* segs, int nseg) { + vmie_mem* m = calloc(1, sizeof *m); + if (!m) { + return NULL; + } + if (gpa_from_ro_fd_segs(m, fd, segs, nseg)) { + free(m); + return NULL; + } + return m; +} + __attribute__((cold)) void vmie_mem_close(vmie_mem* m) { if (!m) { diff --git a/src/core/include/core.h b/src/core/include/core.h index bc6f89d..6b626c7 100644 --- a/src/core/include/core.h +++ b/src/core/include/core.h @@ -20,6 +20,7 @@ typedef struct vmie_mem { int fd; int nseg; gpa_seg seg[VMIE_MAX_SEGS]; + int ro; /* source is read-only (gpa_write/gva_write -> -1); 0 = RW */ } vmie_mem; /* GPA <-> file-offset converters over the segment map. @@ -40,6 +41,15 @@ static inline uintptr_t offset_gpa(const vmie_mem* m, uintptr_t off) { /* guest-physical lifecycle + primitives (gpa.c) */ int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg); int gpa_open (vmie_mem* m, const char* path, uintptr_t low); +/* fd-backed twins of gpa_open*: build from a borrowed fd (dup'd internally, + * caller's fd stays valid) instead of a path. fd must be an mmap-able RW object. */ +int gpa_from_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg); +int gpa_from_fd (vmie_mem* m, int fd, uintptr_t low); +/* read-only twins of gpa_from_fd*: map the borrowed fd PROT_READ and mark the + * source read-only (gpa_write/gva_write -> -1). Accept O_RDONLY as well as + * O_RDWR fds (only read access is required). Reads/gpa_ptr/scan behave as RW. */ +int gpa_from_ro_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg); +int gpa_from_ro_fd (vmie_mem* m, int fd, uintptr_t low); void gpa_close(vmie_mem* m); int gpa_read (vmie_mem* m, uintptr_t offs, void* dst, size_t nmemb); int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, size_t nmemb); diff --git a/src/engine/win32/host.c b/src/engine/win32/host.c index 89c07f3..43fb509 100644 --- a/src/engine/win32/host.c +++ b/src/engine/win32/host.c @@ -21,6 +21,19 @@ vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low) { return v; } +__attribute__((cold)) +vmie_win32* vmie_win32_open_fd(int fd, uint64_t low) { + vmie_win32* v = calloc(1, sizeof *v); + if (!v) { + return NULL; + } + if (gpa_from_fd(&v->mem, fd, low)) { + free(v); + return NULL; + } + return v; +} + __attribute__((cold)) void vmie_win32_close(vmie_win32* v) { if (!v) {