Hi Jeffrey,

On Wed, Sep 23, 2015 at 9:23 PM, Jeffrey Crowell <jcrow...@google.com> wrote:
>
> We have some patches which were created in an attempt to fix some signed
> bugs in the original code that I think may be causing our issue.
>
> Namely here:
> https://github.com/pagespeed/mod_pagespeed/blob/master/third_party/aprutil/apr_memcache2.c#L1453
> vs here
> https://gist.github.com/crowell/59bfa1bb9f0cda30c48a#file-multiget-c-L192
> The original code uses an atoi(), which has undefined behavior when called
> on non integer strings, leaving len to be 0.
>
> Second, the check here
> https://github.com/pagespeed/mod_pagespeed/blob/master/third_party/aprutil/apr_memcache2.c#L1457
> vs https://gist.github.com/crowell/59bfa1bb9f0cda30c48a#file-multiget-c-L199
>
> In the original apr code, this check will never fail. len is an apr_size_t,
> and will never be < 0.

The code you refer has changed with [1] (i.e. APR-1.4.3, the current
version is 1.5.2), but still does not seem to fix the invalid/unknown
length/value/type issue, which are both unexpected errors in
apr_memcache, and as such should probably terminate the connection...

>
> The issue here now is that the check in the "server sent back a key that i
> didn't ask for"
> https://github.com/pagespeed/mod_pagespeed/blob/master/third_party/aprutil/apr_memcache2.c#L1507
> (same in both forked and upstream), apr_pollset_remove, is never called, and
> the number of queries sent is never changed.
>
> Does it seem possible that this is causing the server to spin here

Yes, in the unexpected cases mentioned above, the socket is not
"consumed" (i.e. apr_brigade_partition() is not called), which let
some immediatly poll()able data for the next loops.

> would apr be interested in accepting a patch to fix the signedness issues?

Of course! I included parse_size() in the attached patch which tries
to address the parsing/unexpected issues by aborting the connection
and returning an error.
Does it work for you (it is based on trunk, so you may need to adapt
it to your version, which preferably should be the latest...)?

I'm not sure about the handling of empty values (the real len == 0
case, not the parsing error).
The previous code did not expect the trailing CRLF, while this patch does...

Regards,
Yann.

[1] http://svn.apache.org/r982408
Index: memcache/apr_memcache.c
===================================================================
--- memcache/apr_memcache.c	(revision 1685853)
+++ memcache/apr_memcache.c	(working copy)
@@ -731,6 +731,26 @@ apr_memcache_replace(apr_memcache_t *mc,
 
 }
 
+/*
+ * Parses a decimal size from size_str, returning the value in *size.
+ * Returns 1 if parsing was successful, 0 if parsing failed.
+ */
+static int parse_size(const char *size_str, apr_size_t *size)
+{
+    char *endptr;
+    long size_as_long;
+
+    errno = 0;
+    size_as_long = strtol(size_str, &endptr, 10);
+    if ((size_as_long < 0) || (errno != 0) || (endptr == size_str) ||
+        (endptr[0] != '\r') || (endptr[1] != '\n')) {
+        return 0;
+    }
+
+    *size = (unsigned long)size_as_long;
+    return 1;
+}
+
 APR_DECLARE(apr_status_t)
 apr_memcache_getp(apr_memcache_t *mc,
                   apr_pool_t *p,
@@ -799,14 +819,11 @@ apr_memcache_getp(apr_memcache_t *mc,
         }
 
         length = apr_strtok(NULL, " ", &last);
-        if (length) {
-            len = strtol(length, (char **)NULL, 10);
+        if (!length || !parse_size(length, &len)) {
+            ms_bad_conn(ms, conn);
+            apr_memcache_disable_server(mc, ms);
+            return APR_EINVAL;
         }
-
-        if (len == 0 )  {
-            *new_length = 0;
-            *baton = NULL;
-        }
         else {
             apr_bucket_brigade *bbb;
             apr_bucket *e;
@@ -1361,74 +1378,69 @@ apr_memcache_multgetp(apr_memcache_t *mc,
                char *last;
                char *data;
                apr_size_t len = 0;
+               apr_bucket *e = NULL;
 
                apr_strtok(conn->buffer, " ", &last); /* just the VALUE, ignore */
                key = apr_strtok(NULL, " ", &last);
                flags = apr_strtok(NULL, " ", &last);
+               length = apr_strtok(NULL, " ", &last);
 
-
-               length = apr_strtok(NULL, " ", &last);
-               if (length) {
-                   len = strtol(length, (char **) NULL, 10);
+               if (!length || !parse_size(length, &len)) {
+                   rv = APR_EINVAL;
                }
+               else {
+                   /* eat the trailing \r\n */
+                   rv = apr_brigade_partition(conn->bb, len+2, &e);
+               }
+               if (rv != APR_SUCCESS) {
+                   apr_pollset_remove (pollset, &activefds[i]);
+                   mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
+                                    server_query, values, server_queries);
+                   queries_sent--;
+                   continue;
+               }
 
                value = apr_hash_get(values, key, strlen(key));
-
-               
                if (value) {
-                   if (len != 0)  {
-                       apr_bucket_brigade *bbb;
-                       apr_bucket *e;
-                       
-                       /* eat the trailing \r\n */
-                       rv = apr_brigade_partition(conn->bb, len+2, &e);
-                       
-                       if (rv != APR_SUCCESS) {
-                           apr_pollset_remove (pollset, &activefds[i]);
-                           mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
-                                            server_query, values, server_queries);
-                           queries_sent--;
-                           continue;
-                       }
-                       
-                       bbb = apr_brigade_split(conn->bb, e);
-                       
-                       rv = apr_brigade_pflatten(conn->bb, &data, &len, data_pool);
-                       
-                       if (rv != APR_SUCCESS) {
-                           apr_pollset_remove (pollset, &activefds[i]);
-                           mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
-                                            server_query, values, server_queries);
-                           queries_sent--;
-                           continue;
-                       }
-                       
-                       rv = apr_brigade_destroy(conn->bb);
-                       if (rv != APR_SUCCESS) {
-                           apr_pollset_remove (pollset, &activefds[i]);
-                           mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
-                                            server_query, values, server_queries);
-                           queries_sent--;
-                           continue;
-                       }
-                       
-                       conn->bb = bbb;
-                       
-                       value->len = len - 2;
-                       data[value->len] = '\0';
-                       value->data = data;
+                   apr_bucket_brigade *bbb;
+                  
+                   bbb = apr_brigade_split(conn->bb, e);
+                   
+                   rv = apr_brigade_pflatten(conn->bb, &data, &len,
+                                             data_pool);
+                   if (rv != APR_SUCCESS) {
+                       apr_pollset_remove (pollset, &activefds[i]);
+                       mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
+                                        server_query, values, server_queries);
+                       queries_sent--;
+                       continue;
                    }
                    
+                   rv = apr_brigade_destroy(conn->bb);
+                   if (rv != APR_SUCCESS) {
+                       apr_pollset_remove (pollset, &activefds[i]);
+                       mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
+                                        server_query, values, server_queries);
+                       queries_sent--;
+                       continue;
+                   }
+                   
+                   conn->bb = bbb;
+                   
+                   value->len = len - 2;
+                   data[value->len] = '\0';
+                   value->data = data;
+                   
                    value->status = rv;
                    value->flags = atoi(flags);
                    
                    /* stay on the server */
                    i--;
-                   
                }
                else {
-                   /* TODO: Server Sent back a key I didn't ask for or my
-                    *       hash is corrupt */
+                   /* Server Sent back a key I didn't ask for or my
+                    * hash is corrupt */
+                   rv = APR_EGENERAL;
                }
            }
            else if (strncmp(MS_END, conn->buffer, MS_END_LEN) == 0) {
@@ -1436,7 +1448,6 @@ apr_memcache_multgetp(apr_memcache_t *mc,
                apr_pollset_remove (pollset, &activefds[i]);
                ms_release_conn(ms, conn);
                apr_hash_set(server_queries, &ms, sizeof(ms), NULL);
-               
                queries_sent--;
            }
            else {
@@ -1443,7 +1454,12 @@ apr_memcache_multgetp(apr_memcache_t *mc,
                /* unknown reply? */
                rv = APR_EGENERAL;
            }
-           
+           if (rv != APR_SUCCESS) {
+               apr_pollset_remove (pollset, &activefds[i]);
+               mget_conn_result(FALSE, FALSE, rv, mc, ms, conn,
+                                server_query, values, server_queries);
+               queries_sent--;
+           }
         } /* /for */
     } /* /while */
     

Reply via email to