libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

test_rq_shift_back.c (19706B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2026 Christian Grothoff
      4 
      5      libmicrohttpd is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 2, or (at your
      8      option) any later version.
      9 
     10      libmicrohttpd is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libmicrohttpd; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 /**
     21  * @file test_rq_shift_back.c
     22  * @brief Regression test for the read buffer "shift back" underflow that
     23  *        happened when the last element of the request headers list was
     24  *        a query argument without the '=' character.
     25  *
     26  * The "shift back" optimisation at the end of get_req_headers() re-uses the
     27  * tail of the parsed request header area for the read buffer.  It computes
     28  * the end of the last element of the @a headers_received list as
     29  * "value + value_size".  A query argument given without '=' is stored with
     30  * a NULL @a value, so the computed end was NULL, the resulting shift size
     31  * was huge and the read buffer became a wild pointer.
     32  *
     33  * The optimisation is performed only when the space in the connection
     34  * memory pool is small (MHD_BUF_INC_SIZE > read_buffer_size), therefore the
     35  * daemons are started with a small MHD_OPTION_CONNECTION_MEMORY_LIMIT.
     36  * The tail of the headers list is a query argument only when the request has
     37  * no header lines at all, as the query arguments are added while the request
     38  * line is parsed and the header fields are appended afterwards.
     39  *
     40  * The test honours the daemon option matrix of mhd_opt_matrix.h: when one of
     41  * the MHD_TEST_* environment variables selects a profile, the built-in sweep
     42  * of connection memory limits is replaced by the memory limit of the profile
     43  * and the threading/polling mode of the profile is used as well.  Without
     44  * those variables nothing changes.  As the sub-cases need a pool that can
     45  * still hold the request, a profile asking for less than
     46  * #MIN_POOL_FOR_TEST bytes is raised to that value and the adjustment is
     47  * reported.
     48  *
     49  * @author Christian Grothoff
     50  */
     51 #include "MHD_config.h"
     52 #include "platform.h"
     53 #include <microhttpd.h>
     54 #include <stdlib.h>
     55 #include <string.h>
     56 #include <stdio.h>
     57 #include <errno.h>
     58 
     59 #ifdef HAVE_STRINGS_H
     60 #include <strings.h>
     61 #endif /* HAVE_STRINGS_H */
     62 
     63 #ifndef WINDOWS
     64 #include <unistd.h>
     65 #endif /* ! WINDOWS */
     66 
     67 #ifdef HAVE_SIGNAL_H
     68 #include <signal.h>
     69 #endif /* HAVE_SIGNAL_H */
     70 
     71 #include "mhd_sockets.h" /* only macros used */
     72 #include "mhd_opt_matrix.h"
     73 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this
     74    test into a marked, classifiable test error (TESTING.md, P5). */
     75 #include "mhd_panic_tripwire.h"
     76 
     77 #ifndef MHD_STATICSTR_LEN_
     78 /**
     79  * Determine length of static string / macro strings at compile time.
     80  */
     81 #define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1)
     82 #endif /* ! MHD_STATICSTR_LEN_ */
     83 
     84 /**
     85  * The smallest connection memory pool this test can work with: below that
     86  * MHD cannot even build the reply header and the sub-cases would fail for a
     87  * reason that has nothing to do with the "shift back" code.
     88  */
     89 #define MIN_POOL_FOR_TEST 512
     90 
     91 #if MHD_VERSION >= 0x00097701
     92 #define TEST_MK_RESPONSE(len,str) \
     93   MHD_create_response_from_buffer_static ((len), (str))
     94 #else  /* MHD_VERSION < 0x00097701 */
     95 #define TEST_MK_RESPONSE(len,str) \
     96   MHD_create_response_from_buffer ((len), (void *) (str), \
     97                                    MHD_RESPMEM_PERSISTENT)
     98 #endif /* MHD_VERSION < 0x00097701 */
     99 
    100 /**
    101  * The exit code for a failed test.
    102  */
    103 #define EXIT_FAILED_TEST 1
    104 
    105 /**
    106  * The exit code for a failure of the test framework itself.
    107  */
    108 #define EXIT_HARD_ERROR 99
    109 
    110 /**
    111  * The maximum time (in seconds) to wait for any single socket operation.
    112  */
    113 #define TEST_SOCKET_TIMEOUT_SEC 5
    114 
    115 /**
    116  * The body of the response sent by the test daemon.
    117  */
    118 #define RESP_BODY "OK"
    119 
    120 /**
    121  * The expected start of the response.
    122  */
    123 #define EXPECTED_STATUS "HTTP/1.1 200"
    124 
    125 /**
    126  * Be verbose.
    127  */
    128 static int verbose;
    129 
    130 
    131 _MHD_NORETURN static void
    132 hard_error (const char *desc,
    133             int line_num)
    134 {
    135   fprintf (stderr,
    136            "HARD ERROR: %s at line %d. Last errno: %d (%s).\n",
    137            desc,
    138            line_num,
    139            (int) errno,
    140            strerror (errno));
    141   fflush (stderr);
    142   exit (EXIT_HARD_ERROR);
    143 }
    144 
    145 
    146 #define hardErrorExit(desc) hard_error ((desc), __LINE__)
    147 
    148 
    149 /**
    150  * The handler of the requests, always replies with a tiny static response.
    151  */
    152 static enum MHD_Result
    153 ahc_reply (void *cls,
    154            struct MHD_Connection *connection,
    155            const char *url,
    156            const char *method,
    157            const char *version,
    158            const char *upload_data,
    159            size_t *upload_data_size,
    160            void **req_cls)
    161 {
    162   static int marker;
    163   struct MHD_Response *resp;
    164   enum MHD_Result ret;
    165 
    166   (void) cls; (void) url; (void) method; (void) version;
    167   (void) upload_data;
    168 
    169   if (NULL == *req_cls)
    170   {
    171     *req_cls = &marker;
    172     return MHD_YES;
    173   }
    174   if (0 != *upload_data_size)
    175   {
    176     *upload_data_size = 0;
    177     return MHD_YES;
    178   }
    179   resp = TEST_MK_RESPONSE (MHD_STATICSTR_LEN_ (RESP_BODY),
    180                            RESP_BODY);
    181   if (NULL == resp)
    182     return MHD_NO;
    183   ret = MHD_queue_response (connection,
    184                             MHD_HTTP_OK,
    185                             resp);
    186   MHD_destroy_response (resp);
    187   return ret;
    188 }
    189 
    190 
    191 /**
    192  * Set send and receive timeouts on the socket so that the test cannot
    193  * block indefinitely.
    194  *
    195  * @param sk the socket to set the timeouts on
    196  * @return non-zero if succeed, zero if failed
    197  */
    198 static int
    199 set_socket_timeouts (MHD_socket sk)
    200 {
    201 #if defined(MHD_WINSOCK_SOCKETS)
    202   DWORD tv = (DWORD) (TEST_SOCKET_TIMEOUT_SEC * 1000);
    203 #else  /* ! MHD_WINSOCK_SOCKETS */
    204   struct timeval tv;
    205 
    206   tv.tv_sec = (time_t) TEST_SOCKET_TIMEOUT_SEC;
    207   tv.tv_usec = 0;
    208 #endif /* ! MHD_WINSOCK_SOCKETS */
    209   if (0 != setsockopt (sk,
    210                        SOL_SOCKET,
    211                        SO_RCVTIMEO,
    212                        (const void *) &tv,
    213                        (socklen_t) sizeof (tv)))
    214     return 0;
    215   if (0 != setsockopt (sk,
    216                        SOL_SOCKET,
    217                        SO_SNDTIMEO,
    218                        (const void *) &tv,
    219                        (socklen_t) sizeof (tv)))
    220     return 0;
    221   return ! 0;
    222 }
    223 
    224 
    225 /**
    226  * Open a TCP connection to the given port on the loopback interface.
    227  *
    228  * @param port the port to connect to
    229  * @return the connected socket, MHD_INVALID_SOCKET on failure
    230  */
    231 static MHD_socket
    232 connect_to_port (uint16_t port)
    233 {
    234   MHD_socket sk;
    235   struct sockaddr_in sa;
    236 
    237   sk = socket (PF_INET,
    238                SOCK_STREAM,
    239                0);
    240   if (MHD_INVALID_SOCKET == sk)
    241     return MHD_INVALID_SOCKET;
    242   if (! set_socket_timeouts (sk))
    243   {
    244     (void) MHD_socket_close_ (sk);
    245     return MHD_INVALID_SOCKET;
    246   }
    247   memset (&sa,
    248           0,
    249           sizeof (sa));
    250   sa.sin_family = AF_INET;
    251   sa.sin_port = htons (port);
    252   sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    253   if (0 != connect (sk,
    254                     (const struct sockaddr *) &sa,
    255                     (socklen_t) sizeof (sa)))
    256   {
    257     (void) MHD_socket_close_ (sk);
    258     return MHD_INVALID_SOCKET;
    259   }
    260   return sk;
    261 }
    262 
    263 
    264 /**
    265  * Send all the given data over the socket.
    266  *
    267  * @param sk the socket to use
    268  * @param buf the data to send
    269  * @param size the size of @a buf
    270  * @return non-zero if succeed, zero if failed
    271  */
    272 static int
    273 send_all (MHD_socket sk,
    274           const char *buf,
    275           size_t size)
    276 {
    277   size_t off = 0;
    278 
    279   while (off < size)
    280   {
    281     ssize_t res;
    282 
    283     res = MHD_send_ (sk,
    284                      buf + off,
    285                      size - off);
    286     if (0 > res)
    287     {
    288       if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ()))
    289         continue;
    290       return 0;
    291     }
    292     if (0 == res)
    293       return 0;
    294     off += (size_t) res;
    295   }
    296   return ! 0;
    297 }
    298 
    299 
    300 /**
    301  * Receive data from the socket until @a min_size bytes are received, the
    302  * remote side closes the connection or the receive timeout expires.
    303  *
    304  * @param sk the socket to use
    305  * @param[out] buf the buffer to fill
    306  * @param buf_size the size of @a buf, the result is always zero-terminated,
    307  *                 therefore at most @a buf_size minus one bytes are received
    308  * @param min_size stop receiving as soon as this number of bytes is collected
    309  * @return the number of the received bytes
    310  */
    311 static size_t
    312 recv_reply (MHD_socket sk,
    313             char *buf,
    314             size_t buf_size,
    315             size_t min_size)
    316 {
    317   size_t off = 0;
    318 
    319   while ((off < min_size) && (off + 1 < buf_size))
    320   {
    321     ssize_t res;
    322 
    323     res = MHD_recv_ (sk,
    324                      buf + off,
    325                      buf_size - 1 - off);
    326     if (0 > res)
    327     {
    328       if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ()))
    329         continue;
    330       break; /* Timeout or error */
    331     }
    332     if (0 == res)
    333       break;   /* The remote side closed the connection */
    334     off += (size_t) res;
    335   }
    336   buf[off] = 0;
    337   return off;
    338 }
    339 
    340 
    341 /**
    342  * The description of a single sub-case.
    343  */
    344 struct SubCase
    345 {
    346   /**
    347    * The human readable name of the sub-case.
    348    */
    349   const char *name;
    350 
    351   /**
    352    * The raw request (and any trailing data) to be sent in a single write.
    353    */
    354   const char *request;
    355 };
    356 
    357 
    358 /**
    359  * The sub-cases.
    360  * The sub-cases with a trailing query argument without '=' and without any
    361  * header line are the ones triggering the bug.  The requests with some
    362  * trailing data force a non-zero read buffer offset, which turns the
    363  * underflow into a memmove() with a wild destination pointer (detected
    364  * even when the asserts are disabled), therefore these variants are run
    365  * first.  The control sub-cases are run before them, so that a failure of
    366  * the test harness itself can be distinguished from the bug.
    367  */
    368 static const struct SubCase subcases[] = {
    369   /* Controls, must always succeed. */
    370   { "no query arguments at all (control)",
    371     "GET /a HTTP/1.0\r\n\r\n" },
    372   { "no query arguments at all, with trailing data (control)",
    373     "GET /a HTTP/1.0\r\n\r\nZZZZZZZZ" },
    374   { "empty argument value (control)",
    375     "GET /a?x= HTTP/1.0\r\n\r\n" },
    376   { "empty argument value, with trailing data (control)",
    377     "GET /a?x= HTTP/1.0\r\n\r\nZZZZZZZZ" },
    378   { "non-empty argument value (control)",
    379     "GET /a?x=1 HTTP/1.0\r\n\r\n" },
    380   { "non-empty argument value, with trailing data (control)",
    381     "GET /a?x=1 HTTP/1.0\r\n\r\nZZZZZZZZ" },
    382   { "argument without '=' plus a header line (control)",
    383     "GET /a?x HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" },
    384   { "arguments without '=' plus a header line (control)",
    385     "GET /a?x&y HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" },
    386   { "argument with trailing '&' plus a header line (control)",
    387     "GET /a?x& HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" },
    388   /* The sub-cases triggering the bug. */
    389   { "single argument without '=', with trailing data",
    390     "GET /a?x HTTP/1.0\r\n\r\nZZZZZZZZ" },
    391   { "single argument without '='",
    392     "GET /a?x HTTP/1.0\r\n\r\n" },
    393   { "two arguments, both without '=', with trailing data",
    394     "GET /a?x&y HTTP/1.0\r\n\r\nZZZZZZZZ" },
    395   { "two arguments, both without '='",
    396     "GET /a?x&y HTTP/1.0\r\n\r\n" },
    397   { "argument with value followed by argument without '=', trailing data",
    398     "GET /a?x=1&y HTTP/1.0\r\n\r\nZZZZZZZZ" },
    399   { "argument with value followed by argument without '='",
    400     "GET /a?x=1&y HTTP/1.0\r\n\r\n" },
    401   { "single argument with trailing '&', with trailing data",
    402     "GET /a?x& HTTP/1.0\r\n\r\nZZZZZZZZ" },
    403   { "single argument with trailing '&'",
    404     "GET /a?x& HTTP/1.0\r\n\r\n" },
    405   { "percent-encoded argument name without '=', with trailing data",
    406     "GET /a?%78%79 HTTP/1.0\r\n\r\nZZZZZZZZ" },
    407   { "percent-encoded argument name without '='",
    408     "GET /a?%78%79 HTTP/1.0\r\n\r\n" },
    409   { "plus-encoded argument name without '=', with trailing data",
    410     "GET /a?x+y HTTP/1.0\r\n\r\nZZZZZZZZ" },
    411   { "plus-encoded argument name without '='",
    412     "GET /a?x+y HTTP/1.0\r\n\r\n" }
    413 };
    414 
    415 /**
    416  * The values for MHD_OPTION_CONNECTION_MEMORY_LIMIT to be tested.
    417  * All of them must be small enough to keep the read buffer size below
    418  * MHD_BUF_INC_SIZE (1500), otherwise the "shift back" code is not used
    419  * at all.
    420  */
    421 static const size_t mem_limits[] = { 512, 1024 };
    422 
    423 /**
    424  * The profile selected by the environment, NULL if the built-in sweep is
    425  * used (which is the case for a stock "make check").
    426  */
    427 static const struct MHD_OptMatrixProfile *test_prof;
    428 
    429 /**
    430  * The (possibly adjusted) copy of the profile @a test_prof points to.
    431  */
    432 static struct MHD_OptMatrixProfile test_prof_copy;
    433 
    434 
    435 /**
    436  * Run a single sub-case against the given port.
    437  *
    438  * @param port the port of the test daemon
    439  * @param sc the sub-case to run
    440  * @return non-zero if the sub-case succeeded, zero if it failed
    441  */
    442 static int
    443 run_subcase (uint16_t port,
    444              const struct SubCase *sc)
    445 {
    446   MHD_socket sk;
    447   char buf[1024];
    448   size_t got;
    449   int ret = ! 0;
    450 
    451   sk = connect_to_port (port);
    452   if (MHD_INVALID_SOCKET == sk)
    453     hardErrorExit ("Cannot connect to the test daemon");
    454 
    455   if (! send_all (sk,
    456                   sc->request,
    457                   strlen (sc->request)))
    458   {
    459     fprintf (stderr,
    460              "FAILED: cannot send the request of the sub-case '%s'.\n",
    461              sc->name);
    462     (void) MHD_socket_close_ (sk);
    463     return 0;
    464   }
    465   got = recv_reply (sk,
    466                     buf,
    467                     sizeof (buf),
    468                     MHD_STATICSTR_LEN_ (EXPECTED_STATUS));
    469   if (MHD_STATICSTR_LEN_ (EXPECTED_STATUS) > got)
    470   {
    471     fprintf (stderr,
    472              "FAILED: sub-case '%s': no reply received "
    473              "(got %u bytes, expected at least %u bytes). "
    474              "The daemon may have crashed.\n",
    475              sc->name,
    476              (unsigned) got,
    477              (unsigned) MHD_STATICSTR_LEN_ (EXPECTED_STATUS));
    478     ret = 0;
    479   }
    480   else if (0 != memcmp (buf,
    481                         EXPECTED_STATUS,
    482                         MHD_STATICSTR_LEN_ (EXPECTED_STATUS)))
    483   {
    484     fprintf (stderr,
    485              "FAILED: sub-case '%s': unexpected reply status. "
    486              "Expected '%s', got '%.*s'.\n",
    487              sc->name,
    488              EXPECTED_STATUS,
    489              (int) MHD_STATICSTR_LEN_ (EXPECTED_STATUS),
    490              buf);
    491     ret = 0;
    492   }
    493   else if (verbose)
    494     printf ("PASSED: %s\n",
    495             sc->name);
    496 
    497   (void) MHD_socket_close_ (sk);
    498   return ret;
    499 }
    500 
    501 
    502 /**
    503  * Run all sub-cases with the given connection memory limit.
    504  *
    505  * @param mem_limit the value for MHD_OPTION_CONNECTION_MEMORY_LIMIT
    506  * @return the number of the failed sub-cases
    507  */
    508 static unsigned int
    509 run_with_mem_limit (size_t mem_limit)
    510 {
    511   struct MHD_Daemon *d;
    512   const union MHD_DaemonInfo *dinfo;
    513   uint16_t port;
    514   unsigned int num_failed = 0;
    515   size_t i;
    516   struct MHD_OptMatrixProfile prof;
    517   struct MHD_OptionItem ops[8];
    518   unsigned int flags;
    519 
    520   /* The profile (if any) supplies the discipline level, the threading mode
    521      and the polling backend; the memory limit is the one of the sweep, which
    522      the caller has already taken from the profile. */
    523   if (NULL != test_prof)
    524   {
    525     prof = *test_prof;
    526     prof.mem_limit = mem_limit;
    527   }
    528   else
    529   {
    530     memset (&prof, 0, sizeof (prof));
    531     prof.name = "built-in";
    532     prof.mem_limit = mem_limit;
    533     prof.threading = MHD_OPT_MATRIX_THR_INTERNAL;
    534     prof.poll_backend = MHD_OPT_MATRIX_POLL_SELECT;
    535   }
    536   if (0 == mhd_opt_matrix_fill_options (&prof, ops,
    537                                         (unsigned int) (sizeof (ops)
    538                                                         / sizeof (ops[0]))))
    539     hardErrorExit ("The daemon option array is too small");
    540   /* The client of this test is a plain blocking socket client, so external
    541      polling is served with an internal polling thread instead. */
    542   flags = mhd_opt_matrix_daemon_flags (&prof, MHD_USE_ERROR_LOG, 0);
    543 
    544   d = MHD_start_daemon (flags,
    545                         0,
    546                         NULL, NULL,
    547                         &ahc_reply, NULL,
    548                         MHD_OPTION_ARRAY, ops,
    549                         MHD_OPTION_CONNECTION_TIMEOUT,
    550                         (unsigned int) TEST_SOCKET_TIMEOUT_SEC,
    551                         MHD_OPTION_END);
    552   if (NULL == d)
    553     hardErrorExit ("Cannot start the test daemon");
    554 
    555   dinfo = MHD_get_daemon_info (d,
    556                                MHD_DAEMON_INFO_BIND_PORT);
    557   if ((NULL == dinfo) || (0 == dinfo->port))
    558   {
    559     MHD_stop_daemon (d);
    560     hardErrorExit ("Cannot detect the port used by the test daemon");
    561   }
    562   port = (uint16_t) dinfo->port;
    563 
    564   for (i = 0; i < sizeof (subcases) / sizeof (subcases[0]); ++i)
    565   {
    566     /* Report the sub-case before running it: if the daemon aborts (which is
    567        the expected failure mode of the un-fixed code) the whole test process
    568        is terminated and the last reported sub-case is the failing one. */
    569     fprintf (stderr,
    570              "Running sub-case with memory limit %u: %s\n",
    571              (unsigned) mem_limit,
    572              subcases[i].name);
    573     fflush (stderr);
    574     if (! run_subcase (port,
    575                        subcases + i))
    576       ++num_failed;
    577   }
    578   MHD_stop_daemon (d);
    579   return num_failed;
    580 }
    581 
    582 
    583 int
    584 main (int argc, char *const *argv)
    585 {
    586   unsigned int num_failed = 0;
    587   size_t i;
    588 
    589   verbose = 0;
    590   for (i = 1; i < (size_t) argc; ++i)
    591   {
    592     if ((0 == strcmp (argv[i], "-v")) ||
    593         (0 == strcmp (argv[i], "--verbose")))
    594       verbose = 1;
    595   }
    596 
    597   if (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_AUTOSUPPRESS_SIGPIPE))
    598   {
    599 #if defined(HAVE_SIGNAL_H) && defined(SIGPIPE)
    600     if (SIG_ERR == signal (SIGPIPE, SIG_IGN))
    601       hardErrorExit ("Cannot suppress the SIGPIPE signal");
    602 #else  /* ! HAVE_SIGNAL_H || ! SIGPIPE */
    603     fprintf (stderr,
    604              "Cannot suppress the SIGPIPE signal.\n");
    605 #endif /* ! HAVE_SIGNAL_H || ! SIGPIPE */
    606   }
    607 
    608   mhd_opt_matrix_print_notice ("test_rq_shift_back");
    609   test_prof = mhd_opt_matrix_from_env ();
    610   if (NULL != test_prof)
    611   {
    612     /* A profile pins one configuration: its memory limit replaces the
    613        built-in sweep so that the driver script can walk the matrix. */
    614     char desc[256];
    615 
    616     test_prof_copy = *test_prof;
    617     test_prof = &test_prof_copy;
    618     if (! mhd_opt_matrix_profile_supported (test_prof))
    619     {
    620       printf ("test_rq_shift_back: the selected profile %s is not supported "
    621               "by this build, the test is skipped.\n",
    622               mhd_opt_matrix_describe (test_prof, desc, sizeof (desc)));
    623       return 77;
    624     }
    625     if (mhd_opt_matrix_raise_mem_limit (&test_prof_copy, MIN_POOL_FOR_TEST))
    626       printf ("test_rq_shift_back: the connection memory limit of the "
    627               "profile was raised to %u, the smallest pool this test can "
    628               "work with.\n", (unsigned) MIN_POOL_FOR_TEST);
    629     if (0 == test_prof_copy.mem_limit)
    630       printf ("test_rq_shift_back: NOTE: the profile uses the default "
    631               "connection memory pool, so the \"shift back\" code path is "
    632               "not reached at all by this run.\n");
    633     num_failed = run_with_mem_limit (test_prof_copy.mem_limit);
    634   }
    635   else
    636   {
    637     for (i = 0; i < sizeof (mem_limits) / sizeof (mem_limits[0]); ++i)
    638       num_failed += run_with_mem_limit (mem_limits[i]);
    639   }
    640 
    641   if (0 != num_failed)
    642   {
    643     fprintf (stderr,
    644              "FAILED: %u sub-case(s) failed.\n",
    645              num_failed);
    646     return EXIT_FAILED_TEST;
    647   }
    648   if (verbose)
    649     printf ("All sub-cases passed.\n");
    650   return 0;
    651 }