Author: thebeing Date: Fri Jun 17 11:04:04 2016 New Revision: 39872 URL: http://svn.gna.org/viewcvs/gnustep?rev=39872&view=rev Log: Implement resource limits for regular expression evaluation. Tweaked to roughly match the Cocoa behaviour, but can be changed through the GSRegularExpressionWorkLimit user default.
Modified: libs/base/trunk/ChangeLog libs/base/trunk/Headers/Foundation/NSRegularExpression.h libs/base/trunk/Source/NSRegularExpression.m libs/base/trunk/Tests/base/NSRegularExpression/basic.m Modified: libs/base/trunk/ChangeLog URL: http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/ChangeLog?rev=39872&r1=39871&r2=39872&view=diff ============================================================================== --- libs/base/trunk/ChangeLog (original) +++ libs/base/trunk/ChangeLog Fri Jun 17 11:04:04 2016 @@ -1,3 +1,13 @@ +2016-06-17 Niels Grewe <niels.gr...@halbordnung.de> + + * Headers/Foundation/NSRegularExpression.h + * Source/NSRegularExpression.m + * Tests/base/NSRegularExpression/basic.m: + + Implement resource limits for regular expression evaluation. Tweaked + to roughly match the Cocoa behaviour, but can be changed through + the GSRegularExpressionWorkLimit user default. + 2016-06-17 Niels Grewe <niels.gr...@halbordnung.de> * Source/NSRegularExpression.m: Implement -isEqual: and -hash Modified: libs/base/trunk/Headers/Foundation/NSRegularExpression.h URL: http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Headers/Foundation/NSRegularExpression.h?rev=39872&r1=39871&r2=39872&view=diff ============================================================================== --- libs/base/trunk/Headers/Foundation/NSRegularExpression.h (original) +++ libs/base/trunk/Headers/Foundation/NSRegularExpression.h Fri Jun 17 11:04:04 2016 @@ -1,18 +1,19 @@ + /* Definition of class NSRegularExpression Copyright (C) 2011 Free Software Foundation, Inc. - + This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -72,7 +73,19 @@ #ifndef GSREGEXTYPE # define GSREGEXTYPE void #endif - +/** + * NSRegularExpression is used to inspect and manipulate strings using regular + * expressions. The interface is thread safe: The same NSRegularExpression + * object may be used to concurrently perform matching on multiple threads. + * + * To guard against regular expressions with extremely poor performance, the + * underlying matcher will abort after a certain number of steps. This is + * controlled using the GSRegularExpressionWorkLimit user default. The value of + * this default key represents the number of steps executed by the match engine, + * so it is only indirectly correlated with the time taken to execute the + * pattern, but it usually in the order of milliseconds. The preset 1500, + * setting value to 0 disables the work limit. + */ @interface NSRegularExpression : NSObject <NSCoding, NSCopying> { #if GS_EXPOSE(NSRegularExpression) @@ -153,4 +166,3 @@ #endif /* GS_API_MACOSX */ #endif /* _NSRegualrExpression_h_GNUSTEP_BASE_INCLUDE */ - Modified: libs/base/trunk/Source/NSRegularExpression.m URL: http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Source/NSRegularExpression.m?rev=39872&r1=39871&r2=39872&view=diff ============================================================================== --- libs/base/trunk/Source/NSRegularExpression.m (original) +++ libs/base/trunk/Source/NSRegularExpression.m Fri Jun 17 11:04:04 2016 @@ -46,6 +46,8 @@ #import "Foundation/NSTextCheckingResult.h" #import "Foundation/NSArray.h" #import "Foundation/NSCoder.h" +#import "Foundation/NSUserDefaults.h" +#import "Foundation/NSNotification.h" /** @@ -293,6 +295,48 @@ return stop; } + +#define DEFAULT_WORK_LIMIT 1500 +/** + * The work limit specifies the number of iterations the matcher will do before + * aborting an operation. This ensures that degenerate pattern/input + * combinations don't send the application into what for all intents and + * purposes seems like an infinite loop. + */ +static int32_t _workLimit = DEFAULT_WORK_LIMIT; + ++ (void) _defaultsChanged: (NSNotification*)n +{ + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + id value = [defs objectForKey: @"GSRegularExpressionWorkLimit"]; + int32_t newLimit = DEFAULT_WORK_LIMIT; + if ([value respondsToSelector: @selector(intValue)]) + { + int32_t v = [value intValue]; + if (v >= 0) + { + newLimit = v; + } + } + _workLimit = newLimit; +} + ++ (void) initialize +{ + if (self == [NSRegularExpression class]) + { + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(_defaultsChanged:) + name: NSUserDefaultsDidChangeNotification + object: nil]; + [self _defaultsChanged: nil]; + } +} + + + + /** * Sets up a libicu regex object for use. Note: the documentation states that * NSRegularExpression must be thread safe. To accomplish this, we store a @@ -328,6 +372,7 @@ { uregex_useTransparentBounds(r, TRUE, &s); } + uregex_setTimeLimit(r, _workLimit, &s); if (U_FAILURE(s)) { uregex_close(r); @@ -363,6 +408,7 @@ { uregex_useTransparentBounds(r, TRUE, &s); } + uregex_setTimeLimit(r, _workLimit, &s); if (U_FAILURE(s)) { uregex_close(r); Modified: libs/base/trunk/Tests/base/NSRegularExpression/basic.m URL: http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Tests/base/NSRegularExpression/basic.m?rev=39872&r1=39871&r2=39872&view=diff ============================================================================== --- libs/base/trunk/Tests/base/NSRegularExpression/basic.m (original) +++ libs/base/trunk/Tests/base/NSRegularExpression/basic.m Fri Jun 17 11:04:04 2016 @@ -1,22 +1,102 @@ + #import "ObjectTesting.h" #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSRegularExpression.h> +#import <Foundation/NSDate.h> +#import <Foundation/NSDictionary.h> +#import <Foundation/NSUserDefaults.h> +#import <Foundation/NSRunLoop.h> +#import <Foundation/NSThread.h> +#import <Foundation/NSValue.h> + +@interface DegeneratePatternTest : NSObject +{ + NSRegularExpression *expression; + NSString* input; +} +@end + +@implementation DegeneratePatternTest + +- (instancetype) init +{ + if (nil == (self = [super init])) + { + return nil; + } + expression = + [[NSRegularExpression alloc] initWithPattern: @"^(([a-z])+.)+[A-Z]([a-z])+$" + options: 0 + error: NULL]; + ASSIGN(input, @"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"); + return self; +} + +- (void) runTest: (id)obj +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + [expression matchesInString: input + options: 0 + range: NSMakeRange(0, [input length])]; + DESTROY(pool); +} + +- (void) dealloc +{ + DESTROY(expression); + DESTROY(input); + [super dealloc]; +} +@end + int main() { NSAutoreleasePool *arp = [NSAutoreleasePool new]; +# ifdef GNUSTEP + // Ensure that a deterministic limit is set up for this process + NSUserDefaults *dflts = [NSUserDefaults standardUserDefaults]; + NSDictionary *domain = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: 1500], @"GSRegularExpressionWorkLimit", nil]; + [dflts setVolatileDomain: domain + forName: @"GSTestDomain"]; +# endif id testObj = [[NSRegularExpression alloc] initWithPattern: @"^a" options: 0 error: NULL]; test_NSObject(@"NSRegularExpression", - [NSArray arrayWithObject: + [NSArray arrayWithObject: [[NSRegularExpression alloc] initWithPattern: @"^a" options: 0 error: NULL]]); test_NSCopying(@"NSRegularExpression",@"NSRegularExpression", [NSArray arrayWithObject:testObj],NO,NO); - + + + /* To test whether we correctly bail out of processing degenerate patterns, + * we spin up a new thread and evaluate an expression there. The expectation + * is that the thread should terminate within a few seconds. + * + * NOTE: Since we cannot terminate the thread in case of a failure, this + * test should be run last. + */ + DegeneratePatternTest *test = [DegeneratePatternTest new]; + NSThread *thread = [[NSThread alloc] initWithTarget: test + selector: @selector(runTest:) + object: nil]; + [thread start]; + [thread setName: @"PatternTestRunner"]; + NSDate *started = [NSDate date]; + NSRunLoop *rl = [NSRunLoop currentRunLoop]; + /* We spin the runloop for a bit while we wait for the other thread to bail + * out */ + while ([thread isExecuting] && abs([started timeIntervalSinceNow] < 10.0f)) + { + [rl runMode: NSDefaultRunLoopMode + beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + PASS(NO == [thread isExecuting], "Faulty regular expression terminated"); [arp release]; arp = nil; return 0; } _______________________________________________ Gnustep-cvs mailing list Gnustep-cvs@gna.org https://mail.gna.org/listinfo/gnustep-cvs