NSSecureCoding of collections is not secure

Originator:percysnoodle
Number:rdar://16314084 Date Originated:13 March 2014
Status:Open Resolved:
Product:iOS SDK Product Version:
Classification:Enhancement Reproducible:Always
 
I have an object of class `SGBContainer` which has an array named `objects` that contains objects of class `SGBObject`.  Currently, they each implement NSCoding but not NSSecureCoding.  The `-initWithCoder:` for `SGBContainer` looks like this:

    - (id)initWithCoder:(NSCoder *)aCoder
    {
        self = [self init];
        if (self)
        {
            _objects = [aCoder decodeObjectForKey:@"objects"];
        }
    }
    
I want to switch to using NSSecureCoding, and from what I can tell, it would mean changing the above to this:

    - (id)initWithCoder:(NSCoder *)aCoder
    {
        self = [self init];
        if (self)
        {
            _objects = [aCoder decodeObjectOfClass:[NSArray class] forKey:@"objects"];
        }
    }

...which isn't much of an improvement, as the contents of the array will be instantiated whatever their class.

Comments

There are multiple aspects to this.

1) The (almost) obvious - require secure decoding: In order to make use of NSSecureCoding, it is not enough to use the decodeObjectOfClass: (and similar) methods. One must also require secure decoding, when decoding. Here is a code snippet, that will do the secure decoding:

NSKeyedUnarchiver unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:archivedData]; unarchiver.requiresSecureCoding = YES; MyClass myClass = [unarchiver decodeObjectOfClass:[MyClass class] forKey:@"root"];

2) As I assume, you already did this, here is another tidbit - Apple's classes are considered secure, while your own aren't:

NSArray does decode Apple's own classes, as probably Apple engineers thought, they were "secure" to decode. Probably there is little risk when decoding them, considering the classes themselves. Nevertheless, if I expect an NSString and an NSNumber is decoded, this still would mean a problem for me (potential later crash when accessing -length for example). So this is very undesirable behavior.

If you want do decode an Array of MyClass then, you need to use decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class],[MyClass class],nil]. So secure decoding would otherwise not allow the decoding of MyClass

3) Secure decoding using decodeObjectOfClasses does not protect the structure

If you use the above decodeObjectOfClasses with NSArray and MyClass classes, then the returned structure could be a tuple @[@[],MyClass] or nested @[@[MyClass]] or @[@[@[MyClass]]]. NSSecurecureCoding doe not protect against returning different structures that include the allowed types.

4) This workaround stated in 3) Apple did in the NSKeyedDecoder for NSArrays was done, because in ObjC it was not possible to express something like NSArray (in Java,C++ syntax). Therefore a NSSet of allowed types was returned. Besides the structural problem staten in 3) this also brings the problem (I could not find a (sensible) solution, please prove me, there is one) that for your own collection classes you can not work around it. Thus, if you want to allow decoding of your own collection class Optional, you are stuck. You can decode only (as far as I could figure it out) by allowing NSObjects (decodeObjectOfClass:[NSObject class]) to be decoded. This is crap, as this breaks the structure of your data big time.

By nikolas.mayr at Jan. 21, 2016, 4:38 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!