cipher: Improve error handling.
[libgcrypt.git] / src / sexp.c
index 16def5b..d063962 100644 (file)
@@ -1,7 +1,7 @@
 /* sexp.c  -  S-Expression handling
  * Copyright (C) 1999, 2000, 2001, 2002, 2003,
  *               2004, 2006, 2007, 2008, 2011  Free Software Foundation, Inc.
- * Copyright (C) 2013 g10 Code GmbH
+ * Copyright (C) 2013, 2014 g10 Code GmbH
  *
  * This file is part of Libgcrypt.
  *
 #define GCRYPT_NO_MPI_MACROS 1
 #include "g10lib.h"
 
-typedef struct gcry_sexp *NODE;
+
+/* Notes on the internal memory layout.
+
+   We store an S-expression as one memory buffer with tags, length and
+   value.  The simplest list would thus be:
+
+   /----------+----------+---------+------+-----------+----------\
+   | open_tag | data_tag | datalen | data | close_tag | stop_tag |
+   \----------+----------+---------+------+-----------+----------/
+
+   Expressed more compact and with an example:
+
+   /----+----+----+---+----+----\
+   | OT | DT | DL | D | CT | ST |  "(foo)"
+   \----+----+----+---+----+----/
+
+   The open tag must always be the first tag of a list as requires by
+   the S-expression specs.  At least data element (data_tag, datalen,
+   data) is required as well.  The close_tag finishes the list and
+   would actually be sufficient.  For fail-safe reasons a final stop
+   tag is always the last byte in a buffer; it has a value of 0 so
+   that string function accidentally applied to an S-expression will
+   never access unallocated data.  We do not support display hints and
+   thus don't need to represent them.  A list may have more an
+   arbitrary number of data elements but at least one is required.
+   The length of each data must be greater than 0 and has a current
+   limit to 65535 bytes (by means of the DATALEN type).
+
+   A list with two data elements:
+
+   /----+----+----+---+----+----+---+----+----\
+   | OT | DT | DL | D | DT | DL | D | CT | ST |  "(foo bar)"
+   \----+----+----+---+----+----+---+----+----/
+
+   In the above example both DL fields have a value of 3.
+   A list of a list with one data element:
+
+   /----+----+----+----+---+----+----+----\
+   | OT | OT | DT | DL | D | CT | CT | ST |  "((foo))"
+   \----+----+----+----+---+----+----+----/
+
+   A list with one element followed by another list:
+
+   /----+----+----+---+----+----+----+---+----+----+----\
+   | OT | DT | DL | D | OT | DT | DL | D | CT | CT | ST |  "(foo (bar))"
+   \----+----+----+---+----+----+----+---+----+----+----/
+
+ */
+
 typedef unsigned short DATALEN;
 
 struct gcry_sexp
@@ -42,11 +90,11 @@ struct gcry_sexp
 
 #define ST_STOP  0
 #define ST_DATA  1  /* datalen follows */
-#define ST_HINT  2  /* datalen follows */
+/*#define ST_HINT  2   datalen follows (currently not used) */
 #define ST_OPEN  3
 #define ST_CLOSE 4
 
-/* the atoi macros assume that the buffer has only valid digits */
+/* The atoi macros assume that the buffer has only valid digits.  */
 #define atoi_1(p)   (*(p) - '0' )
 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
@@ -167,9 +215,10 @@ _gcry_sexp_dump (const gcry_sexp_t a)
     }
 }
 
-/****************
- * Pass list through except when it is an empty list - in that case
- * return NULL and release the passed list.
+
+/* Pass list through except when it is an empty list - in that case
+ * return NULL and release the passed list.  This is used to make sure
+ * that no forbidden empty lists are created.
  */
 static gcry_sexp_t
 normalize ( gcry_sexp_t list )
@@ -269,7 +318,7 @@ _gcry_sexp_release( gcry_sexp_t sexp )
 {
   if (sexp)
     {
-      if (gcry_is_secure (sexp))
+      if (_gcry_is_secure (sexp))
         {
           /* Extra paranoid wiping. */
           const byte *p = sexp->d;
@@ -298,7 +347,7 @@ _gcry_sexp_release( gcry_sexp_t sexp )
             }
           wipememory (sexp->d, p - sexp->d);
         }
-      gcry_free ( sexp );
+      xfree ( sexp );
     }
 }
 
@@ -434,7 +483,7 @@ _gcry_sexp_find_token( const gcry_sexp_t list, const char *tok, size_t toklen )
                }
               n = p - head;
 
-              newlist = gcry_malloc ( sizeof *newlist + n );
+              newlist = xtrymalloc ( sizeof *newlist + n );
               if (!newlist)
                 {
                   /* No way to return an error code, so we can only
@@ -501,7 +550,7 @@ _gcry_sexp_length (const gcry_sexp_t list)
 
 
 /* Return the internal lengths offset of LIST.  That is the size of
-   the buffer from the first ST_OPEN, which is retruned at R_OFF, to
+   the buffer from the first ST_OPEN, which is returned at R_OFF, to
    the corresponding ST_CLOSE inclusive.  */
 static size_t
 get_internal_buffer (const gcry_sexp_t list, size_t *r_off)
@@ -542,8 +591,8 @@ get_internal_buffer (const gcry_sexp_t list, size_t *r_off)
 
 
 
-/* Extract the CAR of the given list.  May return NULL for bad lists
-   or memory failure.  */
+/* Extract the n-th element of the given LIST.  Returns NULL for
+   no-such-element, a corrupt list, or memory failure.  */
 gcry_sexp_t
 _gcry_sexp_nth (const gcry_sexp_t list, int number)
 {
@@ -587,15 +636,16 @@ _gcry_sexp_nth (const gcry_sexp_t list, int number)
 
   if (*p == ST_DATA)
     {
-      memcpy (&n, p, sizeof n);
-      p += sizeof n;
-      newlist = gcry_malloc (sizeof *newlist + n + 1);
+      memcpy (&n, p+1, sizeof n);
+      newlist = xtrymalloc (sizeof *newlist + 1 + 1 + sizeof n + n + 1);
       if (!newlist)
         return NULL;
       d = newlist->d;
-      memcpy (d, p, n);
-      d += n;
-      *d++ = ST_STOP;
+      *d++ = ST_OPEN;
+      memcpy (d, p, 1 + sizeof n + n);
+      d += 1 + sizeof n + n;
+      *d++ = ST_CLOSE;
+      *d = ST_STOP;
     }
   else if (*p == ST_OPEN)
     {
@@ -625,7 +675,7 @@ _gcry_sexp_nth (const gcry_sexp_t list, int number)
       } while (level);
       n = p + 1 - head;
 
-      newlist = gcry_malloc (sizeof *newlist + n);
+      newlist = xtrymalloc (sizeof *newlist + n);
       if (!newlist)
         return NULL;
       d = newlist->d;
@@ -639,6 +689,7 @@ _gcry_sexp_nth (const gcry_sexp_t list, int number)
   return normalize (newlist);
 }
 
+
 gcry_sexp_t
 _gcry_sexp_car (const gcry_sexp_t list)
 {
@@ -729,7 +780,7 @@ _gcry_sexp_nth_buffer (const gcry_sexp_t list, int number, size_t *rlength)
   s = do_sexp_nth_data (list, number, &n);
   if (!s || !n)
     return NULL;
-  buf = gcry_malloc (n);
+  buf = xtrymalloc (n);
   if (!buf)
     return NULL;
   memcpy (buf, s, n);
@@ -750,7 +801,7 @@ _gcry_sexp_nth_string (const gcry_sexp_t list, int number)
   s = do_sexp_nth_data (list, number, &n);
   if (!s || n < 1 || (n+1) < 1)
     return NULL;
-  buf = gcry_malloc (n+1);
+  buf = xtrymalloc (n+1);
   if (!buf)
     return NULL;
   memcpy (buf, s, n);
@@ -776,11 +827,11 @@ _gcry_sexp_nth_mpi (gcry_sexp_t list, int number, int mpifmt)
       if (!p)
         return NULL;
 
-      a = gcry_is_secure (list)? _gcry_mpi_snew (0) : _gcry_mpi_new (0);
+      a = _gcry_is_secure (list)? _gcry_mpi_snew (0) : _gcry_mpi_new (0);
       if (a)
         mpi_set_opaque (a, p, n*8);
       else
-        gcry_free (p);
+        xfree (p);
     }
   else
     {
@@ -872,7 +923,7 @@ _gcry_sexp_cdr(const gcry_sexp_t list)
   } while (level);
   n = p - head;
 
-  newlist = gcry_malloc (sizeof *newlist + n + 2);
+  newlist = xtrymalloc (sizeof *newlist + n + 2);
   if (!newlist)
     return NULL;
   d = newlist->d;
@@ -934,7 +985,7 @@ make_space ( struct make_space_ctx *c, size_t n )
       newsize = c->allocated + 2*(n+sizeof(DATALEN)+1);
       if (newsize <= c->allocated)
         return GPG_ERR_TOO_LARGE;
-      newsexp = gcry_realloc ( c->sexp, sizeof *newsexp + newsize - 1);
+      newsexp = xtryrealloc ( c->sexp, sizeof *newsexp + newsize - 1);
       if (!newsexp)
         return gpg_err_code_from_errno (errno);
       c->allocated = newsize;
@@ -1068,6 +1119,13 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
   int arg_counter = 0;
   int level = 0;
 
+  if (!retsexp)
+    return GPG_ERR_INV_ARG;
+  *retsexp = NULL;
+
+  if (!buffer)
+    return GPG_ERR_INV_ARG;
+
   if (!erroff)
     erroff = &dummy_erroff;
 
@@ -1109,10 +1167,10 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
      the provided one.  However, we add space for one extra datalen so
      that the code which does the ST_CLOSE can use MAKE_SPACE */
   c.allocated = length + sizeof(DATALEN);
-  if (buffer && length && gcry_is_secure (buffer))
-    c.sexp = gcry_malloc_secure (sizeof *c.sexp + c.allocated - 1);
+  if (length && _gcry_is_secure (buffer))
+    c.sexp = xtrymalloc_secure (sizeof *c.sexp + c.allocated - 1);
   else
-    c.sexp = gcry_malloc (sizeof *c.sexp + c.allocated - 1);
+    c.sexp = xtrymalloc (sizeof *c.sexp + c.allocated - 1);
   if (!c.sexp)
     {
       err = gpg_err_code_from_errno (errno);
@@ -1342,15 +1400,15 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
                   if (mp && nm)
                     {
                       MAKE_SPACE (nm);
-                      if (!gcry_is_secure (c.sexp->d)
+                      if (!_gcry_is_secure (c.sexp->d)
                           && mpi_get_flag (m, GCRYMPI_FLAG_SECURE))
                         {
                           /* We have to switch to secure allocation.  */
                           gcry_sexp_t newsexp;
                           byte *newhead;
 
-                          newsexp = gcry_malloc_secure (sizeof *newsexp
-                                                        + c.allocated - 1);
+                          newsexp = xtrymalloc_secure (sizeof *newsexp
+                                                       + c.allocated - 1);
                           if (!newsexp)
                             {
                               err = gpg_err_code_from_errno (errno);
@@ -1359,7 +1417,7 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
                           newhead = newsexp->d;
                           memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
                           c.pos = newhead + (c.pos - c.sexp->d);
-                          gcry_free (c.sexp);
+                          xfree (c.sexp);
                           c.sexp = newsexp;
                         }
 
@@ -1375,15 +1433,15 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
                     BUG ();
 
                   MAKE_SPACE (nm);
-                  if (!gcry_is_secure (c.sexp->d)
+                  if (!_gcry_is_secure (c.sexp->d)
                       && mpi_get_flag ( m, GCRYMPI_FLAG_SECURE))
                     {
                       /* We have to switch to secure allocation.  */
                       gcry_sexp_t newsexp;
                       byte *newhead;
 
-                      newsexp = gcry_malloc_secure (sizeof *newsexp
-                                                    + c.allocated - 1);
+                      newsexp = xtrymalloc_secure (sizeof *newsexp
+                                                   + c.allocated - 1);
                       if (!newsexp)
                         {
                           err = gpg_err_code_from_errno (errno);
@@ -1392,7 +1450,7 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
                       newhead = newsexp->d;
                       memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
                       c.pos = newhead + (c.pos - c.sexp->d);
-                      gcry_free (c.sexp);
+                      xfree (c.sexp);
                       c.sexp = newsexp;
                     }
 
@@ -1429,15 +1487,15 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
 
              MAKE_SPACE (alen);
              if (alen
-                  && !gcry_is_secure (c.sexp->d)
-                 && gcry_is_secure (astr))
+                  && !_gcry_is_secure (c.sexp->d)
+                 && _gcry_is_secure (astr))
               {
                  /* We have to switch to secure allocation.  */
                  gcry_sexp_t newsexp;
                  byte *newhead;
 
-                 newsexp = gcry_malloc_secure (sizeof *newsexp
-                                                + c.allocated - 1);
+                 newsexp = xtrymalloc_secure (sizeof *newsexp
+                                               + c.allocated - 1);
                   if (!newsexp)
                     {
                       err = gpg_err_code_from_errno (errno);
@@ -1446,7 +1504,7 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
                  newhead = newsexp->d;
                  memcpy (newhead, c.sexp->d, (c.pos - c.sexp->d));
                  c.pos = newhead + (c.pos - c.sexp->d);
-                 gcry_free (c.sexp);
+                 xfree (c.sexp);
                  c.sexp = newsexp;
                }
 
@@ -1627,12 +1685,10 @@ do_vsexp_sscan (gcry_sexp_t *retsexp, size_t *erroff,
       if (c.sexp)
         {
           /* Extra paranoid wipe on error. */
-          if (gcry_is_secure (c.sexp))
+          if (_gcry_is_secure (c.sexp))
             wipememory (c.sexp, sizeof (struct gcry_sexp) + c.allocated - 1);
-          gcry_free (c.sexp);
+          xfree (c.sexp);
         }
-      /* This might be expected by existing code...  */
-      *retsexp = NULL;
     }
   else
     *retsexp = normalize (c.sexp);
@@ -2113,10 +2169,9 @@ _gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
 }
 
 
-/* Extract MPIs from an s-expression using a list of one letter
- * parameters.  The names of these parameters are given by the string
- * LIST.  Some special characters may be given to control the
- * conversion:
+/* Extract MPIs from an s-expression using a list of parameters.  The
+ * names of these parameters are given by the string LIST.  Some
+ * special characters may be given to control the conversion:
  *
  *    + :: Switch to unsigned integer format (default).
  *    - :: Switch to standard signed format.
@@ -2124,6 +2179,9 @@ _gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
  *    & :: Switch to buffer descriptor mode - see below.
  *    ? :: The previous parameter is optional.
  *
+ * In general parameter names are single letters.  To use a string for
+ * a parameter name, enclose the name in single quotes.
+ *
  * Unless in gcry_buffer_t mode for each parameter name a pointer to
  * an MPI variable is expected and finally a NULL is expected.
  * Example:
@@ -2158,7 +2216,7 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
                            const char *list, va_list arg_ptr)
 {
   gpg_err_code_t rc;
-  const char *s;
+  const char *s, *s2;
   gcry_mpi_t *array[20];
   char arrayisdesc[20];
   int idx;
@@ -2173,10 +2231,23 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
      was found.  */
   for (s=list, idx=0; *s && idx < DIM (array); s++)
     {
-      if (*s == '&' || *s == '+' || *s == '-' || *s == '/' || *s == '?' )
+      if (*s == '&' || *s == '+' || *s == '-' || *s == '/' || *s == '?')
+        ;
+      else if (whitespacep (s))
         ;
       else
         {
+          if (*s == '\'')
+            {
+              s++;
+              s2 = strchr (s, '\'');
+              if (!s2 || s2 == s)
+                {
+                  /* Closing quote not found or empty string.  */
+                  return GPG_ERR_SYNTAX;
+                }
+              s = s2;
+            }
           array[idx] = va_arg (arg_ptr, gcry_mpi_t *);
           if (!array[idx])
             return GPG_ERR_MISSING_VALUE; /* NULL pointer given.  */
@@ -2221,11 +2292,29 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
     {
       if (*s == '&' || *s == '+' || *s == '-' || *s == '/')
         mode = *s;
+      else if (whitespacep (s))
+        ;
       else if (*s == '?')
         ; /* Only used via lookahead.  */
       else
         {
-          l1 = _gcry_sexp_find_token (sexp, s, 1);
+          if (*s == '\'')
+            {
+              /* Find closing quote, find token, set S to closing quote.  */
+              s++;
+              s2 = strchr (s, '\'');
+              if (!s2 || s2 == s)
+                {
+                  /* Closing quote not found or empty string.  */
+                  rc = GPG_ERR_SYNTAX;
+                  goto cleanup;
+                }
+              l1 = _gcry_sexp_find_token (sexp, s, s2 - s);
+              s = s2;
+            }
+          else
+            l1 = _gcry_sexp_find_token (sexp, s, 1);
+
           if (!l1 && s[1] == '?')
             {
               /* Optional element not found.  */
@@ -2316,7 +2405,7 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
           _gcry_mpi_release (*array[idx]);
           *array[idx] = NULL;
         }
-      else if (!arrayisdesc[idx] == 1)
+      else if (arrayisdesc[idx] == 1)
         {
           /* Caller provided buffer.  */
           gcry_buffer_t *spec = (gcry_buffer_t*)array[idx];
@@ -2326,7 +2415,7 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
         {
           /* We might have allocated a buffer.  */
           gcry_buffer_t *spec = (gcry_buffer_t*)array[idx];
-          gcry_free (spec->data);
+          xfree (spec->data);
           spec->data = NULL;
           spec->size = spec->off = spec->len = 0;
         }
@@ -2334,7 +2423,7 @@ _gcry_sexp_vextract_param (gcry_sexp_t sexp, const char *path,
   return rc;
 }
 
-gpg_error_t
+gpg_err_code_t
 _gcry_sexp_extract_param (gcry_sexp_t sexp, const char *path,
                           const char *list, ...)
 {
@@ -2344,5 +2433,5 @@ _gcry_sexp_extract_param (gcry_sexp_t sexp, const char *path,
   va_start (arg_ptr, list);
   rc = _gcry_sexp_vextract_param (sexp, path, list, arg_ptr);
   va_end (arg_ptr);
-  return gpg_error (rc);
+  return rc;
 }