diff --git a/Ample.xcodeproj/project.pbxproj b/Ample.xcodeproj/project.pbxproj index acb95cc..444344c 100644 --- a/Ample.xcodeproj/project.pbxproj +++ b/Ample.xcodeproj/project.pbxproj @@ -90,8 +90,11 @@ B64E15A924EA1D5300E8AD3D /* MachineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B64E15A824EA1D5300E8AD3D /* MachineViewController.m */; }; B66236A924FD9A34006CABD7 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66236A824FD9A34006CABD7 /* PreferencesWindowController.m */; }; B66236B524FDA527006CABD7 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B66236B224FDA522006CABD7 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B66236BC24FDA72E006CABD7 /* mame64 in CopyFiles */ = {isa = PBXBuildFile; fileRef = B66236B724FDA686006CABD7 /* mame64 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; B66236C124FDB7A6006CABD7 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = B66236BF24FDB7A6006CABD7 /* Credits.rtf */; }; + B6841BD7251EC926006A5C39 /* vmnet_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = B6841BCA251EC88E006A5C39 /* vmnet_helper.c */; }; + B6841BDA251ECB1C006A5C39 /* mame64 in CopyFiles */ = {isa = PBXBuildFile; fileRef = B66236B824FDA698006CABD7 /* mame64 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + B6841BDC251ECC17006A5C39 /* vmnet_helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = B6841BD0251EC913006A5C39 /* vmnet_helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + B6841BDE251ECC29006A5C39 /* vmnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6841BDD251ECC29006A5C39 /* vmnet.framework */; }; B6B9EA662506A5550080E70D /* EjectButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B9EA642506A5550080E70D /* EjectButton.m */; }; B6B9EA672506A5550080E70D /* EjectButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B9EA642506A5550080E70D /* EjectButton.m */; }; B6BA258024E99BE9005FB8FF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B6BA257F24E99BE9005FB8FF /* AppDelegate.m */; }; @@ -203,10 +206,20 @@ dstPath = ""; dstSubfolderSpec = 6; files = ( - B66236BC24FDA72E006CABD7 /* mame64 in CopyFiles */, + B6841BDC251ECC17006A5C39 /* vmnet_helper in CopyFiles */, + B6841BDA251ECB1C006A5C39 /* mame64 in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; + B6841BCE251EC913006A5C39 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; B6E4B5F324FDE2670094A35C /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -304,10 +317,12 @@ B66236A724FD9A34006CABD7 /* PreferencesWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; B66236A824FD9A34006CABD7 /* PreferencesWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; B66236B224FDA522006CABD7 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = embedded/SDL2.framework; sourceTree = ""; }; - B66236B724FDA686006CABD7 /* mame64 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = mame64; path = embedded/mame64; sourceTree = ""; }; B66236B824FDA698006CABD7 /* mame64 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = mame64; path = embedded/mame64; sourceTree = ""; }; B66236C024FDB7A6006CABD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = ""; }; B67BD48424EE249D0073E334 /* apple1.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = apple1.plist; sourceTree = ""; }; + B6841BCA251EC88E006A5C39 /* vmnet_helper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = vmnet_helper.c; sourceTree = ""; }; + B6841BD0251EC913006A5C39 /* vmnet_helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = vmnet_helper; sourceTree = BUILT_PRODUCTS_DIR; }; + B6841BDD251ECC29006A5C39 /* vmnet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vmnet.framework; path = System/Library/Frameworks/vmnet.framework; sourceTree = SDKROOT; }; B6B9EA642506A5550080E70D /* EjectButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EjectButton.m; sourceTree = ""; }; B6B9EA652506A5550080E70D /* EjectButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EjectButton.h; sourceTree = ""; }; B6BA257B24E99BE9005FB8FF /* Ample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ample.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -339,6 +354,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + B6841BCD251EC913006A5C39 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B6841BDE251ECC29006A5C39 /* vmnet.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B6BA257824E99BE9005FB8FF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -427,7 +450,7 @@ B66236B624FDA686006CABD7 /* Frameworks */ = { isa = PBXGroup; children = ( - B66236B724FDA686006CABD7 /* mame64 */, + B6841BDD251ECC29006A5C39 /* vmnet.framework */, ); name = Frameworks; sourceTree = ""; @@ -442,11 +465,20 @@ name = "Embedded Content"; sourceTree = ""; }; + B6841BD1251EC913006A5C39 /* vmnet_helper */ = { + isa = PBXGroup; + children = ( + B6841BCA251EC88E006A5C39 /* vmnet_helper.c */, + ); + path = vmnet_helper; + sourceTree = ""; + }; B6BA257224E99BE9005FB8FF = { isa = PBXGroup; children = ( B6BA257D24E99BE9005FB8FF /* Ample */, B66236BD24FDA7EA006CABD7 /* Embedded Content */, + B6841BD1251EC913006A5C39 /* vmnet_helper */, B6BA257C24E99BE9005FB8FF /* Products */, B649798C24EEC165008ABD20 /* Recovered References */, B66236B624FDA686006CABD7 /* Frameworks */, @@ -458,6 +490,7 @@ children = ( B6BA257B24E99BE9005FB8FF /* Ample.app */, B6E4B5FA24FDE2670094A35C /* Ample Lite.app */, + B6841BD0251EC913006A5C39 /* vmnet_helper */, ); name = Products; sourceTree = ""; @@ -553,6 +586,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + B6841BCF251EC913006A5C39 /* vmnet_helper */ = { + isa = PBXNativeTarget; + buildConfigurationList = B6841BD4251EC913006A5C39 /* Build configuration list for PBXNativeTarget "vmnet_helper" */; + buildPhases = ( + B6841BCC251EC913006A5C39 /* Sources */, + B6841BCD251EC913006A5C39 /* Frameworks */, + B6841BCE251EC913006A5C39 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = vmnet_helper; + productName = vmnet_helper; + productReference = B6841BD0251EC913006A5C39 /* vmnet_helper */; + productType = "com.apple.product-type.tool"; + }; B6BA257A24E99BE9005FB8FF /* Ample */ = { isa = PBXNativeTarget; buildConfigurationList = B6BA258C24E99BEB005FB8FF /* Build configuration list for PBXNativeTarget "Ample" */; @@ -599,6 +649,9 @@ LastUpgradeCheck = 1130; ORGANIZATIONNAME = "Kelvin Sherlock"; TargetAttributes = { + B6841BCF251EC913006A5C39 = { + CreatedOnToolsVersion = 11.3.1; + }; B6BA257A24E99BE9005FB8FF = { CreatedOnToolsVersion = 11.3.1; }; @@ -619,6 +672,7 @@ targets = ( B6BA257A24E99BE9005FB8FF /* Ample */, B6E4B5AE24FDE2670094A35C /* Ample Lite */, + B6841BCF251EC913006A5C39 /* vmnet_helper */, ); }; /* End PBXProject section */ @@ -784,6 +838,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + B6841BCC251EC913006A5C39 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B6841BD7251EC926006A5C39 /* vmnet_helper.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B6BA257724E99BE9005FB8FF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -899,6 +961,22 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + B6841BD5251EC913006A5C39 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + B6841BD6251EC913006A5C39 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; B6BA258A24E99BEB005FB8FF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1083,6 +1161,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + B6841BD4251EC913006A5C39 /* Build configuration list for PBXNativeTarget "vmnet_helper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B6841BD5251EC913006A5C39 /* Debug */, + B6841BD6251EC913006A5C39 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; B6BA257624E99BE9005FB8FF /* Build configuration list for PBXProject "Ample" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist b/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist index e3ca695..2322e2a 100644 --- a/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Ample.xcodeproj/xcuserdata/kelvin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -24,6 +24,11 @@ orderHint 0 + vmnet_helper.xcscheme_^#shared#^_ + + orderHint + 2 + diff --git a/vmnet_helper/vmnet_helper.c b/vmnet_helper/vmnet_helper.c new file mode 100644 index 0000000..353db61 --- /dev/null +++ b/vmnet_helper/vmnet_helper.c @@ -0,0 +1,350 @@ +/* vmnet helper */ +/* because it needs root permissions ... sigh */ + +/* + * basicly... run as root, read messages from stdin, write to stdout. + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static interface_ref interface; +static uint8_t interface_mac[6]; +static long interface_mtu; +static long interface_packet_size; +static vmnet_return_t interface_status; + +static size_t buffer_size = 0; +static uint8_t *buffer = NULL; + +enum { + MSG_QUIT, + MSG_STATUS, + MSG_READ, + MSG_WRITE +}; +#define MAKE_MSG(msg, extra) (msg | ((extra) << 8)) + +ssize_t safe_read(void *buffer, size_t nbyte) { + + ssize_t rv; + for(;;) { + rv = read(STDIN_FILENO, buffer, nbyte); + if (rv < 0) { + if (errno == EINTR) continue; + err(1, "read"); + } + break; + } + return rv; +} + + +ssize_t safe_readv(const struct iovec *iov, int iovcnt) { + + ssize_t rv; + for(;;) { + rv = readv(STDIN_FILENO, iov, iovcnt); + if (rv < 0) { + if (errno == EINTR) continue; + err(1, "readv"); + } + break; + } + return rv; +} + +ssize_t safe_write(const void *buffer, size_t nbyte) { + + ssize_t rv; + for(;;) { + rv = write(STDOUT_FILENO, buffer, nbyte); + if (rv < 0) { + if (errno == EINTR) continue; + err(1, "write"); + } + break; + } + return rv; +} + +ssize_t safe_writev(const struct iovec *iov, int iovcnt) { + + ssize_t rv; + for(;;) { + rv = writev(STDOUT_FILENO, iov, iovcnt); + if (rv < 0) { + if (errno == EINTR) continue; + err(1, "writev"); + } + break; + } + return rv; +} + + +void msg_status(uint32_t size) { + struct iovec iov[4]; + + uint32_t msg = MAKE_MSG(MSG_STATUS, 6 + 4 + 4); + + iov[0].iov_len = 4; + iov[0].iov_base = &msg; + + iov[1].iov_len = 6; + iov[1].iov_base = interface_mac; + + iov[2].iov_len = 4; + iov[2].iov_base = &interface_mtu; + + iov[3].iov_len = 4; + iov[3].iov_base = &interface_packet_size; + + + safe_writev(iov, 4); +} + +int classify_mac(uint8_t *mac) { + if ((mac[0] & 0x01) == 0) return 1; /* unicast */ + if (memcmp(mac, "\xff\xff\xff\xff\xff\xff", 6) == 0) return 0xff; /* broadcast */ + return 2; /* multicast */ +} + +void msg_read(uint32_t flags) { + /* flag to block broadcast, multicast, etc? */ + + int count = 1; + int xfer; + vmnet_return_t st; + struct vmpktdesc v; + struct iovec iov[2]; + + uint32_t msg; + + + for(;;) { + int type; + + iov[0].iov_base = buffer; + iov[0].iov_len = interface_packet_size; + + v.vm_pkt_size = interface_packet_size; + v.vm_pkt_iov = iov; + v.vm_pkt_iovcnt = 1; + v.vm_flags = 0; + + count = 1; + st = vmnet_read(interface, &v, &count); + if (st != VMNET_SUCCESS) errx(1, "vmnet_read"); + + if (count < 1) break; + /* todo -- skip multicast messages based on flag? */ + type = classify_mac(buffer); + if (type == 2) continue; /* multicast */ + break; + } + + xfer = count == 1 ? (int)v.vm_pkt_size : 0; + msg = MAKE_MSG(MSG_READ, xfer); + iov[0].iov_len = 4; + iov[0].iov_base = &msg; + iov[1].iov_len = xfer; + iov[1].iov_base = buffer; + + safe_writev(iov, count == 1 ? 2 : 1); +} + + +void msg_write(uint32_t size) { + + ssize_t ok; + + int count = 1; + vmnet_return_t st; + struct vmpktdesc v; + struct iovec iov; + uint32_t msg; + + if (size > interface_packet_size) errx(1, "packet too big"); + for(;;) { + ok = safe_read(buffer, size); + if (ok < 0) err(1,"read"); + if (ok != size) errx(1,"message truncated"); + break; + } + + iov.iov_base = buffer; + iov.iov_len = size; + + v.vm_pkt_size = size; + v.vm_pkt_iov = &iov; + v.vm_pkt_iovcnt = 1; + v.vm_flags = 0; + + st = vmnet_write(interface, &v, &count); + + if (st != VMNET_SUCCESS) errx(1, "vmnet_write"); + + + msg = MAKE_MSG(MSG_WRITE, size); + iov.iov_len = 4; + iov.iov_base = &msg; + + safe_writev(&iov, 1); +} + +/* + * Drop privileges according to the CERT Secure C Coding Standard section + * POS36-C + * https://www.securecoding.cert.org/confluence/display/c/POS36-C.+Observe+correct+revocation+order+while+relinquishing+privileges +*/ +static int drop_privileges(void) { + // If we are not effectively root, don't drop privileges + if (geteuid() != 0 && getegid() != 0) { + return 0; + } + if (setgid(getgid()) == -1) { + return -1; + } + if (setuid(getuid()) == -1) { + return -1; + } + return 0; +} + +void vm_startup(void) { + + xpc_object_t dict; + dispatch_queue_t q; + dispatch_semaphore_t sem; + + + memset(interface_mac, 0, sizeof(interface_mac)); + interface_status = 0; + interface_mtu = 0; + interface_packet_size = 0; + + dict = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_uint64(dict, vmnet_operation_mode_key, VMNET_SHARED_MODE); + sem = dispatch_semaphore_create(0); + q = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); + + interface = vmnet_start_interface(dict, q, ^(vmnet_return_t status, xpc_object_t params){ + interface_status = status; + if (status == VMNET_SUCCESS) { + const char *cp; + cp = xpc_dictionary_get_string(params, vmnet_mac_address_key); + fprintf(stderr, "vmnet mac: %s\n", cp); + sscanf(cp, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &interface_mac[0], + &interface_mac[1], + &interface_mac[2], + &interface_mac[3], + &interface_mac[4], + &interface_mac[5] + ); + + interface_mtu = xpc_dictionary_get_uint64(params, vmnet_mtu_key); + interface_packet_size = xpc_dictionary_get_uint64(params, vmnet_max_packet_size_key); + + fprintf(stderr, "vmnet mtu: %u\n", (unsigned)interface_mtu); + fprintf(stderr, "vmnet packet size: %u\n", (unsigned)interface_packet_size); + + } + dispatch_semaphore_signal(sem); + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + + if (interface_status == VMNET_SUCCESS) { + buffer_size = (interface_packet_size * 2 + 1023) & ~1023; + buffer = (uint8_t *)malloc(buffer_size); + } else { + if (interface) { + vmnet_stop_interface(interface, q, ^(vmnet_return_t status){ + dispatch_semaphore_signal(sem); + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + interface = NULL; + } + errx(1,"vmnet_start_interface failed"); + } + + + dispatch_release(sem); + xpc_release(dict); + drop_privileges(); +} + +void vm_shutdown(void) { + + dispatch_queue_t q; + dispatch_semaphore_t sem; + + + if (interface) { + sem = dispatch_semaphore_create(0); + q = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); + + vmnet_stop_interface(interface, q, ^(vmnet_return_t status){ + dispatch_semaphore_signal(sem); + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + dispatch_release(sem); + + interface = NULL; + interface_status = 0; + } + free(buffer); + buffer = NULL; + buffer_size = 0; + +} + +int main(int argc, char **argv) { + + + uint32_t msg; + uint32_t extra; + ssize_t ok; + + + vm_startup(); + + for(;;) { + ok = safe_read(&msg, 4); + if (ok == 0) break; + if (ok != 4) errx(1,"read msg"); + + extra = msg >> 8; + msg = msg & 0xff; + + switch(msg) { + case MSG_STATUS: + msg_status(extra); + break; + case MSG_QUIT: + vm_shutdown(); + exit(0); + case MSG_READ: + msg_read(extra); + break; + case MSG_WRITE: + msg_write(extra); + break; + } + } + + vm_shutdown(); + exit(0); +}