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

Reply via email to