Author: rfm
Date: Mon Mar 6 18:23:37 2017
New Revision: 40368
URL: http://svn.gna.org/viewcvs/gnustep?rev=40368&view=rev
Log:
Changes for sql injection attack resistance.
Modified:
libs/sqlclient/trunk/ChangeLog
libs/sqlclient/trunk/SQLClient.h
libs/sqlclient/trunk/SQLClient.m
libs/sqlclient/trunk/SQLClientPool.m
Modified: libs/sqlclient/trunk/ChangeLog
URL:
http://svn.gna.org/viewcvs/gnustep/libs/sqlclient/trunk/ChangeLog?rev=40368&r1=40367&r2=40368&view=diff
==============================================================================
--- libs/sqlclient/trunk/ChangeLog (original)
+++ libs/sqlclient/trunk/ChangeLog Mon Mar 6 18:23:37 2017
@@ -1,3 +1,14 @@
+2017-03-06 Richard Frith-Macdonald <[email protected]>
+
+ SQLClient.h:
+ SQLClient.m:
+ SQLClientPool.m:
+ Add new +literal: and -literal: methods to make a normal string into
+ one recognised as suitable for use literally (ie without quoting) in
+ an SQL query/statement.
+ Add +setAutoquote: method to turn on automatic quoting of non-literal
+ strings as an aid to avoiding SQL injectiuon attacks.
+
2016-10-19 Richard Frith-Macdonald <[email protected]>
* Postgres.m: Wolfgang spotted that the asynchronous notification
Modified: libs/sqlclient/trunk/SQLClient.h
URL:
http://svn.gna.org/viewcvs/gnustep/libs/sqlclient/trunk/SQLClient.h?rev=40368&r1=40367&r2=40368&view=diff
==============================================================================
--- libs/sqlclient/trunk/SQLClient.h (original)
+++ libs/sqlclient/trunk/SQLClient.h Mon Mar 6 18:23:37 2017
@@ -488,6 +488,16 @@
*/
+ (SQLClient*) existingClient: (NSString*)reference;
+/** Returns a literal string version of aString.<br />
+ * A literal is an instance of the literal string class produced by the
+ * compiler or the SQLString class (which subclasses it).<br />
+ * All the quoting methods return literal strings.<br />
+ * If aString is already a literal string, this method returns it unchanged,
+ * otherwise the returned value is an autoreleased newly created copy of the
+ * argument.
+ */
++ (NSString*) literal: (NSString*)aString;
+
/**
* Returns the maximum number of simultaneous database connections
* permitted (set by +setMaxConnections: and defaults to 8) for
@@ -513,6 +523,16 @@
* </p>
*/
+ (void) purgeConnections: (NSDate*)since;
+
+/** Turns autoquote on/off for the process.<br />
+ * When autoquote is on, the arguments to the -prepare:args: method (and
+ * therefore all methods that use it) are automatically quoted unless they
+ * are literal strings (see the +literal: method).<br />
+ * The purpose of autoquoting is to help prevent SQL injection attacks on
+ * your software; it helps ensure that only strings you really want to use
+ * literally are embedded in the SQL without quoting.
+ */
++ (void) setAutoquote: (BOOL)aFlag;
/**
* <p>Set the maximum number of simultaneous database connections
@@ -547,6 +567,10 @@
* </p>
*/
- (void) begin;
+
+/** Returns a literal string version of aString (like the +literal: method).
+ */
+- (NSString*) literal: (NSString*)aString;
/** This grabs the receiver for use by the current thread.<br />
* If limit is nil or in the past, makes a single immediate attempt.<br />
@@ -1642,6 +1666,10 @@
name: (NSString*)reference
max: (int)maxConnections
min: (int)minConnections;
+
+/** Returns a literal string version of aString (like the +literal: method).
+ */
+- (NSString*) literal: (NSString*)aString;
/** Returns a long description of the pool including statistics, status,
* and the description of a sample client.
@@ -1959,6 +1987,10 @@
*/
- (void) insertTransaction: (SQLTransaction*)trn atIndex: (unsigned)index;
+/** Returns a literal string version of aString (like the +literal: method).
+ */
+- (NSString*) literal: (NSString*)aString;
+
/**
* Returns the database client with which this instance operates.<br />
* This client is retained by the transaction.<br />
Modified: libs/sqlclient/trunk/SQLClient.m
URL:
http://svn.gna.org/viewcvs/gnustep/libs/sqlclient/trunk/SQLClient.m?rev=40368&r1=40367&r2=40368&view=diff
==============================================================================
--- libs/sqlclient/trunk/SQLClient.m (original)
+++ libs/sqlclient/trunk/SQLClient.m Mon Mar 6 18:23:37 2017
@@ -42,6 +42,7 @@
#import <Foundation/NSMapTable.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSNull.h>
+#import <Foundation/NSObjCRuntime.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSRunLoop.h>
@@ -84,6 +85,72 @@
static Class NSDateClass = Nil;
static Class NSSetClass = Nil;
static Class SQLClientClass = Nil;
+static Class LitStringClass = Nil;
+static Class SQLStringClass = Nil;
+
+static BOOL autoquote = NO;
+
+/* This is the layout of the instance variables of the constant string class
+ * produced by the compiler.
+ * The pointer give us the start of a UTF8 string, so we can create our own
+ * subclass using all the methods of the original class as long as we set
+ * that pointer to a buffer of UTF8 data stored in the object after the
+ * instance variables.
+ */
+@interface SQLString: NSString
+{
+@public
+ char *nxcsptr;
+ unsigned int nxcslen;
+}
+@end
+
+static BOOL
+isLiteral(id obj)
+{
+ if (nil != obj)
+ {
+ Class c = object_getClass(obj);
+
+ if (c == LitStringClass || c == SQLStringClass)
+ {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+static NSString *
+newLiteral(const char *str, unsigned len)
+{
+ SQLString *s;
+
+ s = NSAllocateObject(SQLStringClass, len+1, NSDefaultMallocZone());
+ s->nxcsptr = (char*)&s[1];
+ s->nxcslen = len;
+ memcpy(s->nxcsptr, str, len);
+ s->nxcsptr[len] = '\0';
+ return s;
+}
+
+static NSString *
+literal(NSString *aString)
+{
+ if (nil != aString)
+ {
+ Class c = object_getClass(aString);
+
+ if (c != LitStringClass && c != SQLStringClass)
+ {
+ const char *p = [aString UTF8String];
+ int l = strlen(p);
+ NSString *s = newLiteral(p, l);
+
+ aString = [s autorelease];
+ }
+ }
+ return aString;
+}
@interface SQLClientPool (Swallow)
- (BOOL) _swallowClient: (SQLClient*)client explicit: (BOOL)swallowed;
@@ -94,7 +161,7 @@
stop: (BOOL)stopOnFailure;
@end
-@implementation SQLRecordKeys
+@implementation SQLRecordKeys
- (NSUInteger) count
{
@@ -923,6 +990,19 @@
{
static id modes[1];
+ if (Nil == LitStringClass)
+ {
+ /* Find the literal string class used by the foundation library.
+ */
+ LitStringClass = object_getClass(@"test");
+
+ /* Create the SQLString class as a subclass of that one.
+ */
+ SQLStringClass = (Class)objc_allocateClassPair(
+ LitStringClass, "SQLString", 0);
+ objc_registerClassPair(SQLStringClass);
+ }
+
if (nil == null)
{
null = [NSNull new];
@@ -954,6 +1034,11 @@
repeats: YES];
}
}
+}
+
++ (NSString*) literal: (NSString*)aString
+{
+ return literal(aString);
}
+ (unsigned int) maxConnections
@@ -1078,6 +1163,11 @@
}
}
++ (void) setAutoquote: (BOOL)aFlag
+{
+ autoquote = aFlag;
+}
+
+ (void) setMaxConnections: (unsigned int)c
{
if (c > 0)
@@ -1332,14 +1422,6 @@
[s appendFormat: @" Connected - %@\n", connected ? @"yes" : @"no"];
[s appendFormat: @" Transaction - %@\n",
_inTransaction ? @"yes" : @"no"];
- if (_cache == nil)
- {
- [s appendString: @"\n"];
- }
- else
- {
- [s appendFormat: @" Cache - %@\n", _cache];
- }
}
NS_HANDLER
{
@@ -1542,6 +1624,11 @@
return nil;
}
+- (NSString*) literal: (NSString*)aString
+{
+ return literal(aString);
+}
+
- (BOOL) lockBeforeDate: (NSDate*)limit
{
if (nil == limit)
@@ -1618,26 +1705,46 @@
/*
* Append any values from the nil terminated varargs
*/
- while (tmp != nil)
- {
- if ([tmp isKindOfClass: NSStringClass] == NO)
- {
- if ([tmp isKindOfClass: [NSData class]] == YES)
- {
- [ma addObject: tmp];
- [s appendString: @"'?'''?'"]; // Marker.
- }
- else
- {
- [s appendString: [self quote: tmp]];
- }
- }
- else
- {
- [s appendString: tmp];
- }
- tmp = va_arg(args, NSString*);
- }
+ if (YES == autoquote)
+ {
+ while (tmp != nil)
+ {
+ if ([tmp isKindOfClass: [NSData class]] == YES)
+ {
+ [ma addObject: tmp];
+ tmp = @"'?'''?'"; // Marker.
+ }
+ else if (NO == isLiteral(tmp))
+ {
+ tmp = [self quote: tmp];
+ }
+ [s appendString: tmp];
+ tmp = va_arg(args, NSString*);
+ }
+ }
+ else
+ {
+ while (tmp != nil)
+ {
+ if ([tmp isKindOfClass: NSStringClass] == NO)
+ {
+ if ([tmp isKindOfClass: [NSData class]] == YES)
+ {
+ [ma addObject: tmp];
+ [s appendString: @"'?'''?'"]; // Marker.
+ }
+ else
+ {
+ [s appendString: [self quote: tmp]];
+ }
+ }
+ else
+ {
+ [s appendString: tmp];
+ }
+ tmp = va_arg(args, NSString*);
+ }
+ }
stmt = s;
}
[ma insertObject: stmt atIndex: 0];
@@ -1769,6 +1876,18 @@
{
v = nil; // Mo match found.
}
+ else if (YES == autoquote)
+ {
+ if ([o isKindOfClass: [NSData class]] == YES)
+ {
+ [ma addObject: o];
+ v = @"'?'''?'";
+ }
+ else
+ {
+ v = [self quote: o];
+ }
+ }
else
{
if ([o isKindOfClass: NSStringClass] == YES)
@@ -1863,7 +1982,7 @@
*/
if ([obj isKindOfClass: [NSNumber class]] == YES)
{
- return [obj description];
+ return literal([obj description]);
}
/**
@@ -1872,8 +1991,8 @@
*/
if ([obj isKindOfClass: NSDateClass] == YES)
{
- return [obj descriptionWithCalendarFormat:
- @"'%Y-%m-%d %H:%M:%S.%F %z'" timeZone: nil locale: nil];
+ return literal([obj descriptionWithCalendarFormat:
+ @"'%Y-%m-%d %H:%M:%S.%F %z'" timeZone: nil locale: nil]);
}
/**
@@ -1917,7 +2036,7 @@
[ms appendString: [self quote: value]];
}
[ms appendString: @")"];
- return ms;
+ return literal(ms);
}
/**
@@ -1960,7 +2079,13 @@
- (NSString*) quoteBigInteger: (int64_t)i
{
- return [NSString stringWithFormat: @"%"PRId64, i];
+ char buf[32];
+ unsigned len;
+ NSString *s;
+
+ len = sprintf(buf, "%"PRId64, i);
+ s = newLiteral(buf, len);
+ return [s autorelease];
}
- (NSString*) quoteCString: (const char *)s
@@ -1996,72 +2121,69 @@
- (NSString*) quoteFloat: (float)f
{
- return [NSString stringWithFormat: @"%f", f];
+ char buf[32];
+ unsigned len;
+ NSString *s;
+
+ len = sprintf(buf, "%f", f);
+ s = newLiteral(buf, len);
+ return [s autorelease];
}
- (NSString*) quoteInteger: (int)i
{
- return [NSString stringWithFormat: @"%d", i];
+ char buf[32];
+ unsigned len;
+ NSString *s;
+
+ len = sprintf(buf, "%i", i);
+ s = newLiteral(buf, len);
+ return [s autorelease];
}
- (NSString*) quoteString: (NSString *)s
{
- static NSCharacterSet *special = nil;
- NSMutableString *m;
- NSRange r;
- unsigned l;
-
- if (special == nil)
- {
- NSString *stemp;
-
- /*
- * NB. length of C string is 2, so we include a nul character as a
- * special.
- */
- stemp = [[NSString alloc] initWithBytes: "'"
- length: 2
- encoding: NSASCIIStringEncoding];
- special = [NSCharacterSet characterSetWithCharactersInString: stemp];
- [stemp release];
- [special retain];
- }
-
- /*
- * Step through string removing nul characters
- * and escaping quote characters as required.
- */
- m = [[s mutableCopy] autorelease];
- l = [m length];
- r = NSMakeRange(0, l);
- r = [m rangeOfCharacterFromSet: special options: NSLiteralSearch range: r];
- while (r.length > 0)
- {
- unichar c = [m characterAtIndex: r.location];
-
- if (c == 0)
- {
- r.length = 1;
- [m replaceCharactersInRange: r withString: @""];
- l--;
- }
- else
- {
- r.length = 0;
- [m replaceCharactersInRange: r withString: @"'"];
- l++;
- r.location += 2;
- }
- r = NSMakeRange(r.location, l - r.location);
- r = [m rangeOfCharacterFromSet: special
- options: NSLiteralSearch
- range: r];
- }
-
- /* Add quoting around it. */
- [m replaceCharactersInRange: NSMakeRange(0, 0) withString: @"'"];
- [m appendString: @"'"];
- return m;
+ NSData *d = [s dataUsingEncoding: NSUTF8StringEncoding];
+ const char *src = (const char*)[d bytes];
+ char *dst;
+ unsigned len = [d length];
+ unsigned count = 2;
+ unsigned i;
+ SQLString *q;
+
+ for (i = 0; i < len; i++)
+ {
+ char c = src[i];
+
+ if ('\'' == c)
+ {
+ count++; // A quote needs to be doubled
+ }
+ if ('\0' != c)
+ {
+ count++; // A nul needs to be ignored
+ }
+ }
+ q = NSAllocateObject(SQLStringClass, count + 1, NSDefaultMallocZone());
+ q->nxcsptr = dst = (char*)&q[1];
+ q->nxcslen = count;
+ *dst++ = '\'';
+ for (i = 0; i < len; i++)
+ {
+ char c = src[i];
+
+ if ('\'' == c)
+ {
+ *dst++ = '\'';
+ }
+ if ('\0' != c)
+ {
+ *dst++ = c;
+ }
+ }
+ *dst++ = '\'';
+ *dst = '\0';
+ return [q autorelease];
}
- (oneway void) release
@@ -2957,6 +3079,7 @@
if (nil == _cache)
{
_cache = [GSCache new];
+ [_cache setName: [self clientName]];
if (_cacheThread != nil)
{
[_cache setDelegate: self];
@@ -3751,6 +3874,11 @@
[trn release];
}
+- (NSString*) literal: (NSString*)aString
+{
+ return literal(aString);
+}
+
- (id) owner
{
return _owner;
Modified: libs/sqlclient/trunk/SQLClientPool.m
URL:
http://svn.gna.org/viewcvs/gnustep/libs/sqlclient/trunk/SQLClientPool.m?rev=40368&r1=40367&r2=40368&view=diff
==============================================================================
--- libs/sqlclient/trunk/SQLClientPool.m (original)
+++ libs/sqlclient/trunk/SQLClientPool.m Mon Mar 6 18:23:37 2017
@@ -175,6 +175,11 @@
[self setMax: maxConnections min: minConnections];
}
return self;
+}
+
+- (NSString*) literal: (NSString*)aString
+{
+ return [SQLClient literal: aString];
}
- (NSString*) longDescription
_______________________________________________
Gnustep-cvs mailing list
[email protected]
https://mail.gna.org/listinfo/gnustep-cvs