Fix the potential out of bounds crash for ImageIO incremental animation decoding (like GIF)

This patch from the SDWebImageWebPCoder/pull/68
This commit is contained in:
DreamPiggy 2022-09-26 17:32:25 +08:00
parent 484bc774e1
commit 347cf1d1cc
1 changed files with 52 additions and 7 deletions

View File

@ -13,6 +13,7 @@
#import "SDImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
#import "UIImage+ForceDecode.h"
#import "SDInternalMacros.h"
// Specify DPI for vector format in CGImageSource, like PDF
static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
@ -32,6 +33,8 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
@implementation SDImageIOAnimatedCoder {
size_t _width, _height;
CGImageSourceRef _imageSource;
BOOL _incremental;
SD_LOCK_DECLARE(_lock); // Lock only apply for incremental animation decoding
NSData *_imageData;
CGFloat _scale;
NSUInteger _loopCount;
@ -364,6 +367,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
if (self) {
NSString *imageUTType = self.class.imageUTType;
_imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType});
_incremental = YES;
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
@ -386,6 +390,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
_preserveAspectRatio = preserveAspectRatio;
SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@ -394,6 +399,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
}
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
NSCParameterAssert(_incremental);
if (_finished) {
return;
}
@ -421,11 +427,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
}
}
SD_LOCK(_lock);
// For animated image progressive decoding because the frame count and duration may be changed.
[self scanAndCheckFramesValidWithImageSource:_imageSource];
SD_UNLOCK(_lock);
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
NSCParameterAssert(_incremental);
UIImage *image;
if (_width + _height > 0) {
@ -606,17 +615,21 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
}
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray array];
_loopCount = loopCount;
NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
for (size_t i = 0; i < frameCount; i++) {
SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
frame.index = i;
frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
[frames addObject:frame];
}
if (frames.count != frameCount) {
// frames not match, do not override current value
return NO;
}
_frameCount = frameCount;
_loopCount = loopCount;
_frames = [frames copy];
return YES;
@ -635,16 +648,48 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
if (index >= _frameCount) {
return 0;
NSTimeInterval duration;
// Incremental Animation decoding may update frames when new bytes available
// Which should use lock to ensure frame count and frames match, ensure atomic logic
if (_incremental) {
SD_LOCK(_lock);
if (index >= _frames.count) {
SD_UNLOCK(_lock);
return 0;
}
duration = _frames[index].duration;
SD_UNLOCK(_lock);
} else {
if (index >= _frames.count) {
return 0;
}
duration = _frames[index].duration;
}
return _frames[index].duration;
return duration;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (index >= _frameCount) {
return nil;
UIImage *image;
// Incremental Animation decoding may update frames when new bytes available
// Which should use lock to ensure frame count and frames match, ensure atomic logic
if (_incremental) {
SD_LOCK(_lock);
if (index >= _frames.count) {
SD_UNLOCK(_lock);
return nil;
}
image = [self safeAnimatedImageFrameAtIndex:index];
SD_UNLOCK(_lock);
} else {
if (index >= _frames.count) {
return nil;
}
image = [self safeAnimatedImageFrameAtIndex:index];
}
return image;
}
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
NSDictionary *options;
BOOL forceDecode = NO;
if (@available(iOS 15, tvOS 15, *)) {