Security Framework: unreleased memory

I'm writing software that uses the security framework to get signing information from applications. I noticed that over time memory consumption goes up. After checking with Instruments, I saw an accumulation of objects coming from the internals of the security framework. These allocated objects don't seem to go away.

Following is a standalone code sample that seems to cause the problem in my env. Running it by itself creates a big number of objects that are not released. I also attached a screenshot from Instruments.

#include <Foundation/Foundation.h>
#include <Security/Security.h>

#include <unistd.h>


OSStatus get_signing_info(const char* path)
{
    SecStaticCodeRef codeRef = NULL;
    OSStatus status;

    NSString* str = [NSString stringWithUTF8String:path];
    NSURL* url = [NSURL fileURLWithPath:str];

    status = SecStaticCodeCreateWithPath((__bridge CFURLRef)url, kSecCSDefaultFlags, &codeRef);

    CFDictionaryRef _info = NULL;
    status = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &_info);
    NSDictionary* info = (__bridge_transfer NSDictionary *)_info;
    int flags = [[info objectForKey:(NSString *)kSecCodeInfoFlags] intValue];

    NSLog(@"%d", flags);

    CFRelease(codeRef);

    return status;
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {

        for (int i = 0; i < 1000; ++i) {
            @autoreleasepool {
                OSStatus status = get_signing_info(argv[1]);
                NSLog(@"i=%d, status=%d", i, status);
            }
        }

        sleep(100);

    }
    return 0;
}

Is there a way to get rid of these objects that clog up the memory? Or perhaps use the framework differently to avoid this issue?

Answered by DTS Engineer in 800012022

Following is a standalone code sample that seems to cause the problem in my env. Running it by itself creates a big number of objects that are not released. I also attached a screenshot from Instruments.

I got your code snippet running and there are a few things you should look at first.

  • If you invert the call tree, you'll find that the single largest allocation is a 256kb call to "mach_vm_map" which actually came from "_os_trace_init_slow". That's a "leak" in the sense that the system isn't going to delete it, but that's because it's a tied to a one time initialization that's intentionally never freed.

  • The second largest set is harder to follow but appears to be deep in the internals of the unicode runtime and looks like a similar one-time initialization "charge".

  • Most importantly, I'm not seeing any actual memory growth. Are were my top level results:

1000 Reps:

 607.05 KB      95.7%	3014	 	0x18d713153
 607.05 KB      95.7%	3014	 	 main
  19.62 KB       3.0%	123	 	0x18d712ef3
   7.23 KB       1.1%	26	 	start_wqthread

5000 Reps:

 607.05 KB      96.6%	3014	 	0x18d713153
 607.05 KB      96.6%	3014	 	 main
  19.62 KB       3.1%	123	 	0x18d712ef3
   1.61 KB       0.2%	21	 	start_wqthread

10000 Reps:

 607.00 KB      95.7%	3013	 	0x18d713153
  19.62 KB       3.0%	123	 	0x18d712ef3
   7.19 KB       1.1%	24	 	start_wqthread
  • It's hard to audit every allocation, but there is every indication that all of these are one time initialization of very low level sub-systems (like os_trace and unicode). These subsystems initialize this way because they're so widely used within the entire system that freeing them at all would cause far more performance loss than it would gain.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Following is a standalone code sample that seems to cause the problem in my env. Running it by itself creates a big number of objects that are not released. I also attached a screenshot from Instruments.

I got your code snippet running and there are a few things you should look at first.

  • If you invert the call tree, you'll find that the single largest allocation is a 256kb call to "mach_vm_map" which actually came from "_os_trace_init_slow". That's a "leak" in the sense that the system isn't going to delete it, but that's because it's a tied to a one time initialization that's intentionally never freed.

  • The second largest set is harder to follow but appears to be deep in the internals of the unicode runtime and looks like a similar one-time initialization "charge".

  • Most importantly, I'm not seeing any actual memory growth. Are were my top level results:

1000 Reps:

 607.05 KB      95.7%	3014	 	0x18d713153
 607.05 KB      95.7%	3014	 	 main
  19.62 KB       3.0%	123	 	0x18d712ef3
   7.23 KB       1.1%	26	 	start_wqthread

5000 Reps:

 607.05 KB      96.6%	3014	 	0x18d713153
 607.05 KB      96.6%	3014	 	 main
  19.62 KB       3.1%	123	 	0x18d712ef3
   1.61 KB       0.2%	21	 	start_wqthread

10000 Reps:

 607.00 KB      95.7%	3013	 	0x18d713153
  19.62 KB       3.0%	123	 	0x18d712ef3
   7.19 KB       1.1%	24	 	start_wqthread
  • It's hard to audit every allocation, but there is every indication that all of these are one time initialization of very low level sub-systems (like os_trace and unicode). These subsystems initialize this way because they're so widely used within the entire system that freeing them at all would cause far more performance loss than it would gain.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Security Framework: unreleased memory
 
 
Q