Commit message: fix for bug https://sourceforge.net/p/naviserver/bugs/89/

A negative value provided as chunk encoding length led to a potential crash.
A test case for this case was added to the regression test.
CVE: CVE-2020-13111
CWE: CWE-787
Vulnerability description: NaviServer 4.99.4 to 4.99.19 allows denial of service due to the nsd/driver.c ChunkedDecode function not properly validating the length of a chunk. A remote attacker can craft a chunked-transfer request that will result in a negative value being passed to memmove via the size parameter, causing the process to crash.

Project: naviserver

LookupDriver(Tcl_Interp *interp, const char* protocol, const char *driverName)
{
    Driver *drvPtr;

    NS_NONNULL_ASSERT(interp != NULL);
    NS_NONNULL_ASSERT(protocol != NULL);

    for (drvPtr = firstDrvPtr; drvPtr != NULL;  drvPtr = drvPtr->nextPtr) {
        Ns_Log(DriverDebug, "... check Driver proto <%s> server %s name %s location %s",
               drvPtr->protocol, drvPtr->server, drvPtr->threadName, drvPtr->location);

        if (STREQ(drvPtr->protocol, protocol)) {
            if (driverName == NULL) {
                /*
                 * If there is no driver name given, take the first driver
                 * with the matching protocol.
                 */
                break;
            } else if (STREQ(drvPtr->moduleName, driverName)) {
                /*
                 * The driver name (name of the loaded module) is equal
                 */
                break;
            }
        }
    }

    if (drvPtr == NULL) {
        if (driverName != NULL) {
            Ns_TclPrintfResult(interp, "no driver for protocol '%s' & driver name '%s' found", protocol, driverName);
        } else {
            Ns_TclPrintfResult(interp, "no driver for protocol '%s' found", protocol);
        }
    }
    return drvPtr;
}

Project: naviserver

WriterSockFileVecCleanup(WriterSock *wrSockPtr) {

    NS_NONNULL_ASSERT(wrSockPtr != NULL);

    if ( wrSockPtr->c.file.nbufs > 0) {
        int i;

        Ns_Log(DriverDebug, "WriterSockRelease nbufs %d", wrSockPtr->c.file.nbufs);

        for (i = 0; i < wrSockPtr->c.file.nbufs; i++) {
            /*
             * The fd of c.file.currentbuf is always the same as
             * wrSockPtr->fd and therefore already closed at this point.
             */
            if ( (i != wrSockPtr->c.file.currentbuf)
                 && (wrSockPtr->c.file.bufs[i].fd != NS_INVALID_FD) ) {

                Ns_Log(DriverDebug, "WriterSockRelease must close fd %d",
                       wrSockPtr->c.file.bufs[i].fd);
                ns_close(wrSockPtr->c.file.bufs[i].fd);
            }
        }
        ns_free(wrSockPtr->c.file.bufs);
    }
    ns_free(wrSockPtr->c.file.buf);
}

Project: naviserver

DriverStatsObjCmd(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
{
    int result = TCL_OK;

    if (Ns_ParseObjv(NULL, NULL, interp, 2, objc, objv) != NS_OK) {
        result = TCL_ERROR;

    } else {

        const Driver *drvPtr;
        Tcl_Obj      *resultObj = Tcl_NewListObj(0, NULL);

        /*
         * Iterate over all drivers and collect results.
         */
        for (drvPtr = firstDrvPtr; drvPtr != NULL;  drvPtr = drvPtr->nextPtr) {
            Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);


            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("thread", 6));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(drvPtr->threadName, -1));

            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("module", 6));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(drvPtr->moduleName, -1));

            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("received", 8));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(drvPtr->stats.received));

            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("spooled", 7));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(drvPtr->stats.spooled));

            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("partial", 7));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(drvPtr->stats.partial));

            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("errors", 6));
            Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(drvPtr->stats.errors));

            Tcl_ListObjAppendElement(interp, resultObj, listObj);
        }
        Tcl_SetObjResult(interp, resultObj);
    }

    return result;
}

Project: naviserver

NsGetRequest(Sock *sockPtr, const Ns_Time *nowPtr)
{
    Request *reqPtr;

    NS_NONNULL_ASSERT(sockPtr != NULL);

    /*
     * The underlying "Request" structure is allocated by RequestNew(), which
     * must be called for the "sockPtr" prior to calling this
     * function. "reqPtr" should be NULL just in error cases.
     */
    reqPtr = sockPtr->reqPtr;

    if (likely(reqPtr != NULL)) {

        if (likely(reqPtr->request.line != NULL)) {
            Ns_Log(DriverDebug, "NsGetRequest got the pre-parsed request <%s> from the driver",
                   reqPtr->request.line);

        } else if (sockPtr->drvPtr->requestProc == NULL) {
            /*
             * Non-HTTP driver can send the drvPtr->requestProc to perform
             * their own request handling.
             */
            SockState status;

            Ns_Log(DriverDebug, "NsGetRequest has to read+parse the request");
            /*
             * We have no parsed request so far. So, do it now.
             */
            do {
                Ns_Log(DriverDebug, "NsGetRequest calls SockRead");
                status = SockRead(sockPtr, 0, nowPtr);
            } while (status == SOCK_MORE);

            /*
             * If anything went wrong, clean the request provided by
             * SockRead() and flag the error by returning NULL.
             */
            if (status != SOCK_READY) {
                if (sockPtr->reqPtr != NULL) {
                    Ns_Log(DriverDebug, "NsGetRequest calls RequestFree");
                    RequestFree(sockPtr);
                }
                reqPtr = NULL;
            }
        } else {
            Ns_Log(DriverDebug, "NsGetRequest found driver specific request Proc, "
                   "probably from a non-HTTP driver");
        }
    } else {
        Ns_Log(DriverDebug, "NsGetRequest has reqPtr NULL");
    }

    return reqPtr;
}

Project: naviserver

NsDriverSend(Sock *sockPtr, const struct iovec *bufs, int nbufs, unsigned int flags)
{
    ssize_t       sent = -1;
    const Driver *drvPtr;

    NS_NONNULL_ASSERT(sockPtr != NULL);

    drvPtr = sockPtr->drvPtr;

    NS_NONNULL_ASSERT(drvPtr != NULL);

    if (likely(drvPtr->sendProc != NULL)) {
        /*
         * TODO: The Ns_DriverSendProc signature should be modified
         * to omit the timeout argument. Same with recvProc().
         */
        sent = (*drvPtr->sendProc)((Ns_Sock *) sockPtr, bufs, nbufs, NULL, flags);
    } else {
        Ns_Log(Warning, "no sendProc registered for driver %s", drvPtr->threadName);
    }

    return sent;
}

Project: naviserver

SockError(Sock *sockPtr, SockState reason, int err)
{
    const char *errMsg = NULL;

    NS_NONNULL_ASSERT(sockPtr != NULL);

    switch (reason) {
    case SOCK_READY:
    case SOCK_SPOOL:
    case SOCK_MORE:
    case SOCK_CLOSE:
    case SOCK_CLOSETIMEOUT:
        /* This is normal, never log. */
        break;

    case SOCK_READTIMEOUT:
        /*
         * For this case, whether this is acceptable or not
         * depends upon whether this sock was a keep-alive
         * that we were allowing to 'linger'.
         */
        if (!sockPtr->keep) {
            errMsg = "Timeout during read";
        }
        break;

    case SOCK_WRITETIMEOUT:
        errMsg = "Timeout during write";
        break;

    case SOCK_READERROR:
        errMsg = "Unable to read request";
        break;

    case SOCK_WRITEERROR:
        errMsg = "Unable to write request";
        break;

    case SOCK_SHUTERROR:
        errMsg = "Unable to shutdown socket";
        break;

    case SOCK_BADREQUEST:
        errMsg = "Bad Request";
        SockSendResponse(sockPtr, 400, errMsg);
        break;

    case SOCK_TOOMANYHEADERS:
        errMsg = "Too Many Request Headers";
        SockSendResponse(sockPtr, 414, errMsg);
        break;

    case SOCK_BADHEADER:
        errMsg = "Invalid Request Header";
        SockSendResponse(sockPtr, 400, errMsg);
        break;

    case SOCK_ENTITYTOOLARGE:
        errMsg = "Request Entity Too Large";
        SockSendResponse(sockPtr, 413, errMsg);
        break;

    case SOCK_ERROR:
        errMsg = "Unknown Error";
        SockSendResponse(sockPtr, 400, errMsg);
        break;
    }

    if (errMsg != NULL) {
        char ipString[NS_IPADDR_SIZE];

        Ns_Log(DriverDebug, "SockError: %s (%d: %s), sock: %d, peer: [%s]:%d, request: %.99s",
               errMsg,
               err, (err != 0) ? strerror(err) : NS_EMPTY_STRING,
               sockPtr->sock,
               ns_inet_ntop((struct sockaddr *)&(sockPtr->sa), ipString, sizeof(ipString)),
               Ns_SockaddrGetPort((struct sockaddr *)&(sockPtr->sa)),
               (sockPtr->reqPtr != NULL) ? sockPtr->reqPtr->buffer.string : NS_EMPTY_STRING);
    }
}

Project: naviserver

DriverKeep(Sock *sockPtr)
{
    Ns_DriverKeepProc *keepProc;
    bool              result;

    NS_NONNULL_ASSERT(sockPtr != NULL);

    keepProc = sockPtr->drvPtr->keepProc;
    if (keepProc == NULL)  {
        result = NS_FALSE;
    } else {
        result = (keepProc)((Ns_Sock *) sockPtr);
    }
    return result;
}

Project: naviserver

WriterCheckInputParams(Tcl_Interp *interp, const char *filenameString,
                       size_t size, off_t offset,
                       int *fdPtr, size_t *nrbytesPtr)
{
    int         result = TCL_OK, rc;
    struct stat st;

    Ns_Log(DriverDebug, "WriterCheckInputParams %s offset %" PROTd " size %" PRIdz,
           filenameString, offset, size);

    /*
     * Use stat() call to obtain information about the actual file to check
     * later the plausibility of the parameters.
     */
    rc = stat(filenameString, &st);
    if (unlikely(rc != 0)) {
        Ns_TclPrintfResult(interp, "file does not exist '%s'", filenameString);
        result = TCL_ERROR;

    } else {
        size_t nrbytes = 0u;
        int    fd;

        /*
         * Try to open the file and check offset and size parameters.
         */
        fd = ns_open(filenameString, O_RDONLY | O_CLOEXEC, 0);

        if (unlikely(fd == NS_INVALID_FD)) {
            Ns_TclPrintfResult(interp, "could not open file '%s'", filenameString);
            result = TCL_ERROR;

        } else if (unlikely(offset > st.st_size) || offset < 0) {
            Ns_TclPrintfResult(interp, "offset must be a positive value less or equal filesize");
            result = TCL_ERROR;

        } else if (size > 0) {
            if (unlikely((off_t)size + offset > st.st_size)) {
                Ns_TclPrintfResult(interp, "offset + size must be less or equal filesize");
                result = TCL_ERROR;
            } else {
                nrbytes = (size_t)size;
            }
        } else {
            nrbytes = (size_t)st.st_size - (size_t)offset;
        }

        /*
         * When an offset is provide, jump to this offset.
         */
        if (offset > 0 && result == TCL_OK) {
            if (ns_lseek(fd, (off_t)offset, SEEK_SET) == -1) {
                Ns_TclPrintfResult(interp, "cannot seek to position %ld", (long)offset);
                result = TCL_ERROR;
            }
        }

        if (result == TCL_OK) {
            *fdPtr = fd;
            *nrbytesPtr = nrbytes;

        } else if (fd != NS_INVALID_FD) {
            /*
             * On invalid parameters, close the fd.
             */
            ns_close(fd);
        }
    }

    return result;
}

Project: naviserver

PollWait(const PollData *pdata, int timeout)
{
    int n;

    NS_NONNULL_ASSERT(pdata != NULL);

    do {
        n = ns_poll(pdata->pfds, pdata->nfds, timeout);
    } while (n < 0  && errno == NS_EINTR);

    if (n < 0) {
        Ns_Fatal("PollWait: ns_poll() failed: %s", ns_sockstrerror(ns_sockerrno));
    }
    return n;
}