Protect against overflow of ltree.numlevel and lquery.numlevel.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 28 Mar 2020 21:09:51 +0000 (17:09 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 28 Mar 2020 21:09:51 +0000 (17:09 -0400)
These uint16 fields could be overflowed by excessively long input,
producing strange results.  Complain for invalid input.

Likewise check for out-of-range values of the repeat counts in lquery.
(We don't try too hard on that one, notably not bothering to detect
if atoi's result has overflowed.)

Also detect length overflow in ltree_concat.

In passing, be more consistent about whether "syntax error" messages
include the type name.  Also, clarify the documentation about what
the size limit is.

This has been broken for a long time, so back-patch to all supported
branches.

Nikita Glukhov, reviewed by Benjie Gillam and Tomas Vondra

Discussion: https://postgr.es/m/CAP_rww=waX2Oo6q+MbMSiZ9ktdj6eaJj0cQzNu=Ry2cCDij5fw@mail.gmail.com

contrib/ltree/expected/ltree.out
contrib/ltree/ltree.h
contrib/ltree/ltree_io.c
contrib/ltree/ltree_op.c
contrib/ltree/sql/ltree.sql
doc/src/sgml/ltree.sgml

index 822693090567919f9ac453377594c93326eb571d..41e7f947875b7e7ca990c799b9e2639956a00409 100644 (file)
@@ -457,6 +457,52 @@ SELECT nlevel('1.2.3.4');
       4
 (1 row)
 
+SELECT nlevel(('1' || repeat('.1', 65534))::ltree);
+ nlevel 
+--------
+  65535
+(1 row)
+
+SELECT nlevel(('1' || repeat('.1', 65535))::ltree);
+ERROR:  number of ltree levels (65536) exceeds the maximum allowed (65535)
+SELECT nlevel(('1' || repeat('.1', 65534))::ltree || '1');
+ERROR:  number of ltree levels (65536) exceeds the maximum allowed (65535)
+SELECT ('1' || repeat('.1', 65534))::lquery IS NULL;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT ('1' || repeat('.1', 65535))::lquery IS NULL;
+ERROR:  number of lquery levels (65536) exceeds the maximum allowed (65535)
+SELECT '*{65535}'::lquery;
+  lquery  
+----------
+ *{65535}
+(1 row)
+
+SELECT '*{65536}'::lquery;
+ERROR:  lquery syntax error
+LINE 1: SELECT '*{65536}'::lquery;
+               ^
+DETAIL:  Low limit (65536) exceeds the maximum allowed (65535).
+SELECT '*{,65534}'::lquery;
+  lquery   
+-----------
+ *{,65534}
+(1 row)
+
+SELECT '*{,65535}'::lquery;
+ lquery 
+--------
+ *
+(1 row)
+
+SELECT '*{,65536}'::lquery;
+ERROR:  lquery syntax error
+LINE 1: SELECT '*{,65536}'::lquery;
+               ^
+DETAIL:  High limit (65536) exceeds the maximum allowed (65535).
 SELECT '1.2'::ltree  < '2.2'::ltree;
  ?column? 
 ----------
index e4b8c84fa621e78615b11cde10c9e833c30efeca..e4716be7ed5ec08ec2c43c06c88f075b1f0684e7 100644 (file)
@@ -25,6 +25,7 @@ typedef struct
 
 #define LTREE_HDRSIZE  MAXALIGN( offsetof(ltree, data) )
 #define LTREE_FIRST(x) ( (ltree_level*)( ((char*)(x))+LTREE_HDRSIZE ) )
+#define LTREE_MAX_LEVELS   PG_UINT16_MAX   /* ltree.numlevel is uint16 */
 
 
 /* lquery */
@@ -77,6 +78,7 @@ typedef struct
 
 #define LQUERY_HDRSIZE  MAXALIGN( offsetof(lquery, data) )
 #define LQUERY_FIRST(x)   ( (lquery_level*)( ((char*)(x))+LQUERY_HDRSIZE ) )
+#define LQUERY_MAX_LEVELS  PG_UINT16_MAX   /* lquery.numlevel is uint16 */
 
 #define LQUERY_HASNOT      0x01
 
index f54f03744365b0202f2ea325e8fb9589bb97b6ae..0a7d9aa0bdda0aafe81f8c5a7d68ff283a3720d4 100644 (file)
@@ -58,11 +58,11 @@ ltree_in(PG_FUNCTION_ARGS)
        ptr += charlen;
    }
 
-   if (num + 1 > MaxAllocSize / sizeof(nodeitem))
+   if (num + 1 > LTREE_MAX_LEVELS)
        ereport(ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                errmsg("number of levels (%d) exceeds the maximum allowed (%d)",
-                       num + 1, (int) (MaxAllocSize / sizeof(nodeitem)))));
+                errmsg("number of ltree levels (%d) exceeds the maximum allowed (%d)",
+                       num + 1, LTREE_MAX_LEVELS)));
    list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (num + 1));
    ptr = buf;
    while (*ptr)
@@ -227,11 +227,11 @@ lquery_in(PG_FUNCTION_ARGS)
    }
 
    num++;
-   if (num > MaxAllocSize / ITEMSIZE)
+   if (num > LQUERY_MAX_LEVELS)
        ereport(ERROR,
                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                errmsg("number of levels (%d) exceeds the maximum allowed (%d)",
-                       num, (int) (MaxAllocSize / ITEMSIZE))));
+                errmsg("number of lquery levels (%d) exceeds the maximum allowed (%d)",
+                       num, LQUERY_MAX_LEVELS)));
    curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * num);
    ptr = buf;
    while (*ptr)
@@ -344,7 +344,7 @@ lquery_in(PG_FUNCTION_ARGS)
            else if (charlen == 1 && t_iseq(ptr, '.'))
            {
                curqlevel->low = 0;
-               curqlevel->high = 0xffff;
+               curqlevel->high = LTREE_MAX_LEVELS;
                curqlevel = NEXTLEV(curqlevel);
                state = LQPRS_WAITLEVEL;
            }
@@ -357,7 +357,16 @@ lquery_in(PG_FUNCTION_ARGS)
                state = LQPRS_WAITSNUM;
            else if (t_isdigit(ptr))
            {
-               curqlevel->low = atoi(ptr);
+               int         low = atoi(ptr);
+
+               if (low < 0 || low > LTREE_MAX_LEVELS)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("lquery syntax error"),
+                            errdetail("Low limit (%d) exceeds the maximum allowed (%d).",
+                                      low, LTREE_MAX_LEVELS)));
+
+               curqlevel->low = (uint16) low;
                state = LQPRS_WAITND;
            }
            else
@@ -367,12 +376,21 @@ lquery_in(PG_FUNCTION_ARGS)
        {
            if (t_isdigit(ptr))
            {
-               curqlevel->high = atoi(ptr);
+               int         high = atoi(ptr);
+
+               if (high < 0 || high > LTREE_MAX_LEVELS)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("lquery syntax error"),
+                            errdetail("High limit (%d) exceeds the maximum allowed (%d).",
+                                      high, LTREE_MAX_LEVELS)));
+
+               curqlevel->high = (uint16) high;
                state = LQPRS_WAITCLOSE;
            }
            else if (charlen == 1 && t_iseq(ptr, '}'))
            {
-               curqlevel->high = 0xffff;
+               curqlevel->high = LTREE_MAX_LEVELS;
                state = LQPRS_WAITEND;
            }
            else
@@ -422,7 +440,7 @@ lquery_in(PG_FUNCTION_ARGS)
        if (lptr->start == ptr)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("syntax error"),
+                    errmsg("lquery syntax error"),
                     errdetail("Unexpected end of line.")));
 
        lptr->len = ptr - lptr->start -
@@ -432,7 +450,7 @@ lquery_in(PG_FUNCTION_ARGS)
        if (lptr->len == 0)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("syntax error"),
+                    errmsg("lquery syntax error"),
                     errdetail("Unexpected end of line.")));
 
        if (lptr->wlen > 255)
@@ -444,11 +462,11 @@ lquery_in(PG_FUNCTION_ARGS)
                               lptr->wlen, pos)));
    }
    else if (state == LQPRS_WAITOPEN)
-       curqlevel->high = 0xffff;
+       curqlevel->high = LTREE_MAX_LEVELS;
    else if (state != LQPRS_WAITEND)
        ereport(ERROR,
                (errcode(ERRCODE_SYNTAX_ERROR),
-                errmsg("syntax error"),
+                errmsg("lquery syntax error"),
                 errdetail("Unexpected end of line.")));
 
    curqlevel = tmpql;
@@ -468,8 +486,8 @@ lquery_in(PG_FUNCTION_ARGS)
        else if (curqlevel->low > curqlevel->high)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("syntax error"),
-                    errdetail("Low limit(%d) is greater than upper(%d).",
+                    errmsg("lquery syntax error"),
+                    errdetail("Low limit (%d) is greater than upper (%d).",
                               curqlevel->low, curqlevel->high)));
 
        curqlevel = NEXTLEV(curqlevel);
@@ -593,7 +611,7 @@ lquery_out(PG_FUNCTION_ARGS)
            }
            else if (curqlevel->low == 0)
            {
-               if (curqlevel->high == 0xffff)
+               if (curqlevel->high == LTREE_MAX_LEVELS)
                {
                    *ptr = '*';
                    *(ptr + 1) = '\0';
@@ -601,7 +619,7 @@ lquery_out(PG_FUNCTION_ARGS)
                else
                    sprintf(ptr, "*{,%d}", curqlevel->high);
            }
-           else if (curqlevel->high == 0xffff)
+           else if (curqlevel->high == LTREE_MAX_LEVELS)
            {
                sprintf(ptr, "*{%d,}", curqlevel->low);
            }
index df61c63180c261deb4c9712c93f079f9b53a62a5..51545e0ef46101c675d461f04021ca23d20a437d 100644 (file)
@@ -274,10 +274,17 @@ static ltree *
 ltree_concat(ltree *a, ltree *b)
 {
    ltree      *r;
+   int         numlevel = (int) a->numlevel + b->numlevel;
+
+   if (numlevel > LTREE_MAX_LEVELS)
+       ereport(ERROR,
+               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                errmsg("number of ltree levels (%d) exceeds the maximum allowed (%d)",
+                       numlevel, LTREE_MAX_LEVELS)));
 
    r = (ltree *) palloc0(VARSIZE(a) + VARSIZE(b) - LTREE_HDRSIZE);
    SET_VARSIZE(r, VARSIZE(a) + VARSIZE(b) - LTREE_HDRSIZE);
-   r->numlevel = a->numlevel + b->numlevel;
+   r->numlevel = (uint16) numlevel;
 
    memcpy(LTREE_FIRST(r), LTREE_FIRST(a), VARSIZE(a) - LTREE_HDRSIZE);
    memcpy(((char *) LTREE_FIRST(r)) + VARSIZE(a) - LTREE_HDRSIZE,
index 846b04e48eefbec141eadadf959b07f94ccecb6f..fca3ae645d24a1b95fd0ab96a202a09be79bacf1 100644 (file)
@@ -90,6 +90,17 @@ SELECT '1.*.4|3|2.*{1}'::lquery;
 SELECT 'qwerty%@*.tu'::lquery;
 
 SELECT nlevel('1.2.3.4');
+SELECT nlevel(('1' || repeat('.1', 65534))::ltree);
+SELECT nlevel(('1' || repeat('.1', 65535))::ltree);
+SELECT nlevel(('1' || repeat('.1', 65534))::ltree || '1');
+SELECT ('1' || repeat('.1', 65534))::lquery IS NULL;
+SELECT ('1' || repeat('.1', 65535))::lquery IS NULL;
+SELECT '*{65535}'::lquery;
+SELECT '*{65536}'::lquery;
+SELECT '*{,65534}'::lquery;
+SELECT '*{,65535}'::lquery;
+SELECT '*{,65536}'::lquery;
+
 SELECT '1.2'::ltree  < '2.2'::ltree;
 SELECT '1.2'::ltree  <= '2.2'::ltree;
 SELECT '2.2'::ltree  = '2.2'::ltree;
index 3ddd335b8c929e29ec992de9f0471d1e9376a247..847aaac1f6ecad5682986c1a74bdd736edf349a1 100644 (file)
@@ -31,8 +31,7 @@
    A <firstterm>label path</firstterm> is a sequence of zero or more
    labels separated by dots, for example <literal>L1.L2.L3</literal>, representing
    a path from the root of a hierarchical tree to a particular node.  The
-   length of a label path must be less than 65kB, but keeping it under 2kB is
-   preferable.
+   length of a label path cannot exceed 65535 labels.
   </para>
 
   <para>