libmicrohttpd

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

commit b32f9a0e3d53386702d33cd23acc28815fa5b4a0
parent b25731eb2555aed201955633bd57d810b70b5001
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  4 Sep 2016 12:35:46 +0000

-fixing minor issues (leaks, use after free) in recently added upgrade logic

Diffstat:
Mdoc/libmicrohttpd.texi | 2+-
Msrc/microhttpd/daemon.c | 145++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/microhttpd/internal.h | 2+-
Msrc/microhttpd/response.c | 20++++++++++++--------
Msrc/microhttpd/test_upgrade_ssl.c | 22+++++++++++++++++-----
5 files changed, 142 insertions(+), 49 deletions(-)

diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi @@ -161,7 +161,7 @@ Examples based on reports we've received from developers include: @cindex select MHD supports four basic thread modes and up to three event loop -styes. +styles. The four basic thread modes are external (MHD creates no threads, event loop is fully managed by the application), internal (MHD creates diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -653,25 +653,29 @@ urh_to_fdset (struct MHD_UpgradeResponseHandle *urh, MHD_socket *max_fd, unsigned int fd_setsize) { - if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) && + if ( (urh->out_buffer_off < urh->out_buffer_size) && + (MHD_INVALID_SOCKET != urh->mhd.socket) && (! MHD_add_to_fd_set_ (urh->mhd.socket, rs, max_fd, fd_setsize)) ) return MHD_NO; if ( (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) && + (MHD_INVALID_SOCKET != urh->mhd.socket) && (! MHD_add_to_fd_set_ (urh->mhd.socket, ws, max_fd, fd_setsize)) ) return MHD_NO; - if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) && + if ( (urh->in_buffer_off < urh->in_buffer_size) && + (MHD_INVALID_SOCKET != urh->connection->socket_fd) && (! MHD_add_to_fd_set_ (urh->connection->socket_fd, rs, max_fd, fd_setsize)) ) return MHD_NO; if ( (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) && + (MHD_INVALID_SOCKET != urh->connection->socket_fd) && (! MHD_add_to_fd_set_ (urh->connection->socket_fd, ws, max_fd, @@ -966,6 +970,12 @@ process_urh (struct MHD_UpgradeResponseHandle *urh) { urh->in_buffer_off += res; } + if (0 == res) + { + /* connection was shut down, signal by shrinking buffer, + which will eventually ensure this FD is no longer listed. */ + urh->in_buffer_size = urh->in_buffer_off; + } } if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) && (urh->in_buffer_off > 0) ) @@ -977,8 +987,18 @@ process_urh (struct MHD_UpgradeResponseHandle *urh) urh->in_buffer_off); if (-1 == res) { - /* FIXME: differenciate by errno? */ - urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY; + int err = MHD_socket_get_error_ (); + + if ( (MHD_SCKT_ERR_IS_EINTR_ (err)) || + (MHD_SCKT_ERR_IS_EAGAIN_ (err)) ) + urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY; + else + { + /* persistent / unrecoverable error, treat as + if connection was shut down */ + urh->in_buffer_size = 0; + urh->in_buffer_off = 0; + } } else { @@ -1014,6 +1034,12 @@ process_urh (struct MHD_UpgradeResponseHandle *urh) { urh->out_buffer_off += res; } + if (0 == res) + { + /* connection was shut down, signal by shrinking buffer, + which will eventually ensure this FD is no longer listed. */ + urh->out_buffer_size = urh->out_buffer_off; + } fin_read = (0 == res); } else @@ -1045,6 +1071,13 @@ process_urh (struct MHD_UpgradeResponseHandle *urh) urh->out_buffer_off = 0; } } + else + { + /* persistent / unrecoverable error, treat as + if connection was shut down */ + urh->out_buffer_size = 0; + urh->out_buffer_off = 0; + } } if ( (fin_read) && (0 == urh->out_buffer_off) && @@ -1064,20 +1097,10 @@ static void thread_main_connection_upgrade (struct MHD_Connection *con) { struct MHD_Daemon *daemon = con->daemon; + struct MHD_UpgradeResponseHandle *urh = con->urh; - if (0 == (daemon->options & MHD_USE_SSL)) - { - /* Here, we need to block until the application - signals us that it is done with the socket */ - MHD_semaphore_down (con->upgrade_sem); - MHD_semaphore_destroy (con->upgrade_sem); - con->upgrade_sem = NULL; - return; - } #if HTTPS_SUPPORT { - struct MHD_UpgradeResponseHandle *urh = con->urh; - /* Here, we need to bi-directionally forward until the application tells us that it is done with the socket; */ @@ -1108,11 +1131,14 @@ thread_main_connection_upgrade (struct MHD_Connection *con) #endif break; } - num_ready = MHD_SYS_select_ (max_fd + 1, - &rs, - &ws, - NULL, - NULL); + if (MHD_INVALID_SOCKET != max_fd) + num_ready = MHD_SYS_select_ (max_fd + 1, + &rs, + &ws, + NULL, + NULL); + else + num_ready = 0; if (num_ready < 0) { const int err = MHD_socket_get_error_(); @@ -1131,31 +1157,37 @@ thread_main_connection_upgrade (struct MHD_Connection *con) &rs, &ws); process_urh (urh); + if ( (0 == urh->out_buffer_size) && + (0 == urh->in_buffer_size) ) + break; /* connections died, we have no more purpose here */ } } #ifdef HAVE_POLL else { /* use poll() */ - struct pollfd p[2]; const unsigned int timeout = UINT_MAX; - p[0].fd = urh->connection->socket_fd; - p[1].fd = urh->mhd.socket; while (MHD_CONNECTION_UPGRADE == con->state) { - if (0 == (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) + struct pollfd p[2]; + + memset (p, 0, sizeof (struct pollfd) * 2); + p[0].fd = urh->connection->socket_fd; + p[1].fd = urh->mhd.socket; + if (urh->in_buffer_off < urh->in_buffer_size) p[0].events |= POLLIN; if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) p[0].events |= POLLOUT; - if (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) + if (urh->out_buffer_off < urh->out_buffer_size) p[1].events |= POLLIN; if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) p[1].events |= POLLOUT; - if (MHD_sys_poll_ (p, - 2, - timeout) < 0) + if ( (0 != (p[0].events | p[1].events)) && + (MHD_sys_poll_ (p, + 2, + timeout) < 0) ) { const int err = MHD_socket_get_error_ (); @@ -1177,6 +1209,9 @@ thread_main_connection_upgrade (struct MHD_Connection *con) if (0 != (p[1].revents & POLLOUT)) urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY; process_urh (urh); + if ( (0 == urh->out_buffer_size) && + (0 == urh->in_buffer_size) ) + break; /* connections died, we have no more purpose here */ } } /* end POLL */ @@ -1187,6 +1222,13 @@ thread_main_connection_upgrade (struct MHD_Connection *con) MHD_PANIC ("This should not be possible\n"); #endif } + + /* Here, we need to block until the application + signals us that it is done with the socket */ + MHD_semaphore_down (con->upgrade_sem); + MHD_semaphore_destroy (con->upgrade_sem); + con->upgrade_sem = NULL; + free (urh); } @@ -2598,6 +2640,7 @@ MHD_run_from_select (struct MHD_Daemon *daemon, struct MHD_Connection *next; #if HTTPS_SUPPORT struct MHD_UpgradeResponseHandle *urh; + struct MHD_UpgradeResponseHandle *urhn; #endif unsigned int mask = MHD_USE_SUSPEND_RESUME | MHD_USE_EPOLL_INTERNALLY | MHD_USE_SELECT_INTERNALLY | MHD_USE_POLL_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION; @@ -2650,8 +2693,9 @@ MHD_run_from_select (struct MHD_Daemon *daemon, /* handle upgraded HTTPS connections */ #if HTTPS_SUPPORT - for (urh = daemon->urh_head; NULL != urh; urh = urh->next) + for (urh = daemon->urh_head; NULL != urh; urh = urhn) { + urhn = urh->next; /* update urh state based on select() output */ urh_from_fdset (urh, read_fd_set, @@ -2934,13 +2978,13 @@ MHD_poll_all (struct MHD_Daemon *daemon, for (urh = daemon->urh_head; NULL != urh; urh = urh->next) { p[poll_server+i].fd = urh->connection->socket_fd; - if (0 == (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) + if (urh->in_buffer_off < urh->in_buffer_size) p[poll_server+i].events |= POLLIN; if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) p[poll_server+i].events |= POLLOUT; i++; p[poll_server+i].fd = urh->mhd.socket; - if (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) + if (urh->out_buffer_off < urh->out_buffer_size) p[poll_server+i].events |= POLLIN; if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) p[poll_server+i].events |= POLLOUT; @@ -2952,7 +2996,9 @@ MHD_poll_all (struct MHD_Daemon *daemon, free(p); return MHD_YES; } - if (MHD_sys_poll_(p, poll_server + num_connections, timeout) < 0) + if (MHD_sys_poll_(p, + poll_server + num_connections, + timeout) < 0) { const int err = MHD_socket_get_error_ (); if (MHD_SCKT_ERR_IS_EINTR_ (err)) @@ -2988,17 +3034,20 @@ MHD_poll_all (struct MHD_Daemon *daemon, next = pos->next; /* first, sanity checks */ if (i >= num_connections) - continue; /* connection list changed somehow, retry later ... */ + break; /* connection list changed somehow, retry later ... */ if (p[poll_server+i].fd != pos->socket_fd) continue; /* fd mismatch, something else happened, retry later ... */ call_handlers (pos, 0 != (p[poll_server+i].revents & POLLIN), 0 != (p[poll_server+i].revents & POLLOUT), MHD_NO); + i++; } #if HTTPS_SUPPORT for (urh = daemon->urh_head; NULL != urh; urh = urhn) { + if (i >= num_connections) + break; /* connection list changed somehow, retry later ... */ urhn = urh->next; if (p[poll_server+i].fd != urh->connection->socket_fd) continue; /* fd mismatch, something else happened, retry later ... */ @@ -3148,6 +3197,7 @@ run_epoll_for_upgrade (struct MHD_Daemon *daemon) struct epoll_event events[MAX_EVENTS]; int num_events; unsigned int i; + unsigned int j; num_events = MAX_EVENTS; while (MAX_EVENTS == num_events) @@ -3172,7 +3222,34 @@ run_epoll_for_upgrade (struct MHD_Daemon *daemon) for (i=0;i<(unsigned int) num_events;i++) { struct UpgradeEpollHandle *ueh = events[i].data.ptr; - struct MHD_UpgradeResponseHandle *urh = ueh->urh; + struct MHD_UpgradeResponseHandle *urh; + + if (NULL == ueh) + continue; /* was killed, see below */ + urh = ueh->urh; + + /* In case we get two events for the same upgrade handle, + squash them together (otherwise the first one may + cause us to free the 'urh', and the second one then + causes a use-after-free). */ + for (j=i+1;j< (unsigned int) num_events;j++) + { + struct UpgradeEpollHandle *uehj = events[j].data.ptr; + struct MHD_UpgradeResponseHandle *urhj; + + if (NULL == uehj) + continue; /* was killed, see below */ + urhj = uehj->urh; + + if (urh == urhj) /* yep, indeed the same! */ + { + if (0 != (events[j].events & EPOLLIN)) + uehj->celi |= MHD_EPOLL_STATE_READ_READY; + if (0 != (events[j].events & EPOLLOUT)) + uehj->celi |= MHD_EPOLL_STATE_WRITE_READY; + } + events[j].data.ptr = NULL; /* kill this one */ + } /* Update our state based on what is ready according to epoll() */ if (0 != (events[i].events & EPOLLIN)) diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -859,7 +859,6 @@ struct MHD_Connection */ TransmitCallback send_cls; -#if HTTPS_SUPPORT /** * If this connection was upgraded and if we are using * #MHD_USE_THREAD_PER_CONNECTION, this points to the @@ -869,6 +868,7 @@ struct MHD_Connection */ struct MHD_UpgradeResponseHandle *urh; +#if HTTPS_SUPPORT /** * If this connection was upgraded and if we are using * #MHD_USE_THREAD_PER_CONNECTION without encryption, diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c @@ -627,19 +627,16 @@ MHD_upgrade_action (struct MHD_UpgradeResponseHandle *urh, /* Application is done with this connection, tear it down! */ if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION) ) { - if (0 == (daemon->options & MHD_USE_SSL) ) - { - /* just need to signal the thread that we are done */ - MHD_semaphore_up (connection->upgrade_sem); - } #if HTTPS_SUPPORT - else + if (0 != (daemon->options & MHD_USE_SSL) ) { - /* signal thread by shutdown() of 'app' socket */ + /* signal thread that app is done by shutdown() of 'app' socket */ shutdown (urh->app.socket, SHUT_RDWR); } #endif + /* need to signal the thread that we are done */ + MHD_semaphore_up (connection->upgrade_sem); return MHD_YES; } #if HTTPS_SUPPORT @@ -755,6 +752,12 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response, urh->mhd.celi = MHD_EPOLL_STATE_UNREADY; pool = connection->pool; avail = MHD_pool_get_free (pool); + if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION) ) + { + /* Need to give the thread something to block on... */ + connection->upgrade_sem = MHD_semaphore_create (0); + } + if (avail < 8) { /* connection's pool is totally at the limit, @@ -871,7 +874,8 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response, { /* Need to give the thread something to block on... */ connection->upgrade_sem = MHD_semaphore_create (0); - if (NULL == connection->upgrade_sem) + connection->urh = urh; + if (NULL == connection->upgrade_sem) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, diff --git a/src/microhttpd/test_upgrade_ssl.c b/src/microhttpd/test_upgrade_ssl.c @@ -400,7 +400,9 @@ test_upgrade_internal (int flags, MHD_socket sock; pid_t pid; - d = MHD_start_daemon (flags | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME | MHD_USE_TLS, + if (0 == (flags & MHD_USE_THREAD_PER_CONNECTION)) + flags |= MHD_USE_SUSPEND_RESUME; + d = MHD_start_daemon (flags | MHD_USE_DEBUG | MHD_USE_TLS, 1080, NULL, NULL, &ahc_upgrade, NULL, @@ -442,25 +444,35 @@ main (int argc, if (0 != system ("openssl version 1> /dev/null")) return 77; /* openssl not available, can't run the test */ + + /* Test thread-per-connection */ + error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION, + 0); + error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL, + 0); + + /* Test different event loops, with and without thread pool */ error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, - 1); + 0); error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, 2); #ifdef HAVE_POLL error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, - 1); + 0); error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, 2); #endif #ifdef EPOLL_SUPPORT error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY | MHD_USE_TLS_EPOLL_UPGRADE, - 1); + 0); error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY | MHD_USE_TLS_EPOLL_UPGRADE, 2); #endif - if (error_count != 0) + + /* report result */ + if (0 != error_count) fprintf (stderr, "Error (code: %u)\n", error_count);