SOLVED I made a 'system ran out of application memory' virus by accident, please help.

Samuel Venable

Time Killer
Objective-C:
#include <string>
#include <cstring>

#include <Cocoa/Cocoa.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>

#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))

namespace {

void convert_bgra_to_rgba(const uint8_t *BGRA, uint32_t width, uint32_t height, uint8_t **RGBA) {
  int offset = 0;
  uint8_t *result = *RGBA;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      result[offset + 0] = BGRA[offset + 2];
      result[offset + 1] = BGRA[offset + 1];
      result[offset + 2] = BGRA[offset + 0];
      result[offset + 3] = BGRA[offset + 3];
      offset += 4;
    }
  }
  *RGBA = result;
}

void window_get_size_from_id(char *window, int *width, int *height) {
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      CFDictionaryRef windowInfoDictionary =
      (CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i);
      CFNumberRef windowID = (CFNumberRef)CFDictionaryGetValue(
      windowInfoDictionary, kCGWindowNumber);
      CGWindowID wid; CFNumberGetValue(windowID,
      kCGWindowIDCFNumberType, &wid);
      if (strtoull(window, nullptr, 10) == wid) {
        CGRect rect; CFDictionaryRef dict = (CFDictionaryRef)CFDictionaryGetValue(
        windowInfoDictionary, kCGWindowBounds);
        CGRectMakeWithDictionaryRepresentation(dict, &rect);
        *width = CGRectGetWidth(rect);
        *height = CGRectGetHeight(rect);
      }
    }
  }
  CFRelease(windowArray);
}

} // anonymous namespace

EXPORTED_FUNCTION char *window_id_from_native_window(char *native) {
  static std::string window;
  window = std::to_string([(NSWindow *)(void *)strtoull(native, nullptr, 10) windowNumber]);
  return (char *)window.c_str();
}

EXPORTED_FUNCTION char *native_window_from_window_id(char *window) {
  static std::string native;
  native = std::to_string((unsigned long long)(void *)[NSApp windowWithWindowNumber:strtoull(window, nullptr, 10)]);
  return (char *)native.c_str();
}

EXPORTED_FUNCTION double window_get_width_from_id(char *window) {
  int width = 0, height = 0;
  window_get_size_from_id(window, &width, &height);
  return width;
}

EXPORTED_FUNCTION double window_get_height_from_id(char *window) {
  int width = 0, height = 0;
  window_get_size_from_id(window, &width, &height);
  return height;
}

EXPORTED_FUNCTION double window_grab_frame_buffer(char *window, char *buffer) {
  CGImageRef image = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, strtoull(window, nullptr, 10), kCGWindowImageBoundsIgnoreFraming);
  if (image) {
    uint8_t *dst = (uint8_t *)buffer;
    CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image));
    if (data) {
      UInt8 *src = (UInt8 *)malloc(4 * CGImageGetWidth(image) * CGImageGetHeight(image));
      if (src) {
        CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), src);
        convert_bgra_to_rgba((uint8_t *)src, CGImageGetWidth(image), CGImageGetHeight(image), &dst);
        free(src);
      }
      CFRelease(data);
    }
  }
  return 0;
}
I left my extension running for a while on my Mac, and noticed a 'system ran out of application memory' error and a "Force Quit..." dialog appeared and my extension testing app (in this case, the YoYo Runner) was using excessive amounts of memory and was the culprit of the problem, rooted more specifically in my extension. The window_grab_frame_buffer() function is probably what's the issue narrowing it down entirely. window_grab_frame_buffer() is supposed to capture a window using the CGWindowID owned by an external app process id I executed as a child process from my main app process.

While after googling that error, it appears this is a type of virus I wrote by accident which eats all your system application memory. However it's not actually dangerous in my case because it is not running in daemon mode and isn't hidden from the "Force Quit..." dialog, you just force the runner to quit as soon as it stops responding and it's like nothing ever happened, so at least I'm not doing anything damaging to the user like I feared after publishing this update.

However your Mac might get really hot while the extension is running, not so much so any damage to the machine is to be at risk, Macs intentionally prefer being "sexy" by being silent and disabling the fan from blowing as hard as any normal computer would to keep it from wear and tear over the course of 10 years, which is why older Macs are more prone to stop working quicker than older pc's with a proper functioning fan. So there's a lot of things that can make your Mac get hot, this is relatively normal and doesn't concern me, they made the machine this way intentionally to create minimal fan noise. Nothing magical here.

Anyway the extension code is above. This is included in my Virtual Visit Maker asset as of the most recent update. Don't be afraid to download it so you can inspect what's going on In the GML side more in depth. But don't run it if I unnecessarily scared you with the big bad word "virus" :p. I'm still pretty certain the link I read that used that word virus in my google search was very different kind of app than the one I wrote, so I feel like saying "virus" for the lack of a better word.

I chose to use the word virus as clickbait hoping people would be more likely/willing to read this thread and possibly help sooner. Because I want this fixed before I get negative reviews. You're welcome. :p

Now for the GML side, leaving out most of the unrelated code:

GML:
function vvm_drawgui() {
  if (os_type == os_macosx) {
    // Check for valid CGWindowID printed.
    // Prints -1 on failure to get CGWindowID.
    if (global.vvm_window != string(-1)) {
      draw_set_color(c_white);
      display_set_gui_size(
      window_get_width(), window_get_height());
      // Create surface with CGWindowID resolution:
      if (!surface_exists(global.vvm_surface) &&
        global.vvm_width != 0 && global.vvm_height != 0) {
        global.vvm_surface = surface_create(global.vvm_width, global.vvm_height);
        surface_set_target(global.vvm_surface);
        draw_clear_alpha(c_black, 0);
        surface_reset_target();
      }
      // Copy buffer pixel data to surface if any exists:
      if (buffer_exists(global.vvm_buffer) && surface_exists(global.vvm_surface)) {
        buffer_set_surface(global.vvm_buffer, global.vvm_surface, 0);
        draw_surface_stretched(global.vvm_surface, 0, 0,
        window_get_width(), window_get_height());
      }
    }
  }
}

function vvm_endstep() {
  // Force windowed mode
  if (window_get_fullscreen())
  window_set_fullscreen(false);
  // Copy pixel data from CGWindowID to buffer.
  if (os_type == os_macosx) {
    if (global.vvm_window != string(-1) &&
    buffer_exists(global.vvm_buffer)) {
      window_grab_frame_buffer(global.vvm_window,
      buffer_get_address(global.vvm_buffer));
    }
  }
  // parse stdout string by every line feed character, read last / most recent line for specific strings.
  var output = string_split(ExecutedProcessReadFromStandardOutput(global.vvm_process), "\n");
  if (array_length(output) >= 2) {
    global.vvm_lastline = output[array_length(output) - 2];
    // close child process of it prints "Forced Quit..." to stdout.
    if (string_count("Forced Quit...", global.vvm_lastline) >= 1) {
       FreeExecutedProcessStandardInput(global.vvm_process);
       FreeExecutedProcessStandardOutput(global.vvm_process);
       game_end();
    // Create window capture buffer from CGWindowID printed to stdout.
    } else if (!global.vvm_initwid && vvm_window_identifier() != string(-1)) {
      global.vvm_window  = vvm_window_identifier();
      if (os_type == os_macosx) {
        global.vvm_width = int64(EnvironmentGetVariable("WINDOW_WIDTH"));
        global.vvm_height = int64(EnvironmentGetVariable("WINDOW_HEIGHT"));
        global.vvm_surface = -1;
        if (global.vvm_width == 0 && global.vvm_height == 0)
        return; global.vvm_chan = buffer_sizeof(buffer_u64);
        global.vvm_buffer = buffer_create(global.vvm_chan *
        global.vvm_width * global.vvm_height,
        buffer_fixed, global.vvm_chan);
        buffer_poke(global.vvm_buffer, buffer_get_size(global.vvm_buffer) - 1, buffer_u8, 0);
      }
      global.vvm_initwid = true;
    }
  }
  // free memory for recorded stdin/stdout strings.
  if (global.vvm_process != 0 && CompletionStatusFromExecutedProcess(global.vvm_process)) {
    FreeExecutedProcessStandardInput(global.vvm_process);
    FreeExecutedProcessStandardOutput(global.vvm_process);
    game_end();
  }
  global.vvm_hotspotid = -1;
}

// Call on Game End Event.
// Don't call on Cleanup Event:
function vvm_uninit() {
  if (os_type == os_macosx) {
    if (buffer_exists(global.vvm_buffer))
    buffer_delete(global.vvm_buffer);
    if (surface_exists(global.vvm_surface))
    surface_free(global.vvm_surface);
  }
  if (global.vvm_process != 0 && !CompletionStatusFromExecutedProcess(global.vvm_process)) {
    ProcIdKill(global.vvm_process);
    FreeExecutedProcessStandardInput(global.vvm_process);
    FreeExecutedProcessStandardOutput(global.vvm_process);
  }
}
P.S. Please don't report my asset because you didn't read far enough in this thread to understand I called it a "virus" as clickbait. :p
Thanks,
Samuel

Edit:

Updated my Objective-C++ code, there were several things wrong with it I disovered, including obviously I forgot to free the CGImageRef

Now, here's the *.mm file contents:

Objective-C:
#include <string>

#include <cstdlib>

#include <Cocoa/Cocoa.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>

#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))

namespace {

void window_get_size_from_id(char *window, int *width, int *height) {
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      CFDictionaryRef windowInfoDictionary =
      (CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i);
      CFNumberRef windowID = (CFNumberRef)CFDictionaryGetValue(
      windowInfoDictionary, kCGWindowNumber);
      CGWindowID wid; CFNumberGetValue(windowID,
      kCGWindowIDCFNumberType, &wid);
      if (strtoull(window, nullptr, 10) == wid) {
        CGRect rect; CFDictionaryRef dict = (CFDictionaryRef)CFDictionaryGetValue(
        windowInfoDictionary, kCGWindowBounds);
        CGRectMakeWithDictionaryRepresentation(dict, &rect);
        *width = CGRectGetWidth(rect);
        *height = CGRectGetHeight(rect);  
      }
    }
  }
  CFRelease(windowArray);
}

void copy_pixeldata(unsigned char *in, size_t width, size_t height, unsigned char **out) {
  int offset = 0;
  unsigned char *result = *out;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      result[offset + 0] = in[offset + 1];
      result[offset + 1] = in[offset + 2];
      result[offset + 2] = in[offset + 3];
      result[offset + 3] = in[offset + 0];
      offset += 4;
    }
  }
  *out = result;
}

} // anonymous namespace

EXPORTED_FUNCTION char *window_id_from_native_window(char *native) {
  static std::string window;
  window = std::to_string([(NSWindow *)(void *)strtoull(native, nullptr, 10) windowNumber]);
  return (char *)window.c_str();
}

EXPORTED_FUNCTION char *native_window_from_window_id(char *window) {
  static std::string native;
  native = std::to_string((unsigned long long)(void *)[NSApp windowWithWindowNumber:strtoull(window, nullptr, 10)]);
  return (char *)native.c_str();
}

EXPORTED_FUNCTION double window_get_width_from_id(char *window) {
  int width = 0, height = 0;
  window_get_size_from_id(window, &width, &height);
  return width;
}

EXPORTED_FUNCTION double window_get_height_from_id(char *window) {
  int width = 0, height = 0;
  window_get_size_from_id(window, &width, &height);
  return height;
}

EXPORTED_FUNCTION double window_grab_frame_buffer(char *window, char *buffer) {
  CGImageRef cgimage = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, strtoull(window, nullptr, 10), kCGWindowImageBoundsIgnoreFraming);
  if (cgimage) {
    NSBitmapImageRep *nsbitmaprep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
    if (nsbitmaprep) {
      unsigned char *dst = (unsigned char *)buffer;
      copy_pixeldata([nsbitmaprep bitmapData],
      CGImageGetWidth(cgimage), CGImageGetHeight(cgimage), &dst);
      [nsbitmaprep release];
    }
    CGImageRelease(cgimage);
  }
  return 0;
}
This should be much simpler, however, I am still getting a super hot mac, and major lagging in my app. I haven't come across the "system out of application memory" error or "Force Quit..." dialog box yet, but I'll try leaving the app running longer this time and see if it does it still.

Edit2:

Yep, while it still does lag intermittently, that appears to be unrelated. It definitely appears all that it was, was a CGImageRef memory leak.

Problem solved!
 
Last edited:
Top