Commit message: CVE-2019-14889: scp: Reformat scp.c

Fixes T181

Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit 42c727d0c186a1e2fa84a31ab40e16e58b404ab3)
CVE: CVE-2019-14889
CWE: CWE-78
Vulnerability description: A flaw was found with the libssh API function ssh_scp_new() in versions before 0.9.3 and before 0.8.8. When the libssh SCP client connects to a server, the scp command, which includes a user-provided path, is executed on the server-side. In case the library is used in a way where users can influence the third parameter of the function, it would become possible for an attacker to inject arbitrary commands, leading to a compromise of the remote target.

Project: libssh

int ssh_scp_integer_mode(const char *mode)
{
    int value = strtoul(mode, NULL, 8) & 0xffff;
    return value;
}

Project: libssh

int ssh_scp_read(ssh_scp scp, void *buffer, size_t size)
{
    int rc;
    int code;

    if (scp == NULL) {
        return SSH_ERROR;
    }

    if (scp->state == SSH_SCP_READ_REQUESTED &&
        scp->request_type == SSH_SCP_REQUEST_NEWFILE)
    {
        rc = ssh_scp_accept_request(scp);
        if (rc == SSH_ERROR) {
            return rc;
        }
    }

    if (scp->state != SSH_SCP_READ_READING) {
        ssh_set_error(scp->session, SSH_FATAL,
                      "ssh_scp_read called under invalid state");
        return SSH_ERROR;
    }

    if (scp->processed + size > scp->filelen) {
        size = (size_t) (scp->filelen - scp->processed);
    }

    if (size > 65536) {
        size = 65536; /* avoid too large reads */
    }

    rc = ssh_channel_read(scp->channel, buffer, size, 0);
    if (rc != SSH_ERROR) {
        scp->processed += rc;
    } else {
        scp->state = SSH_SCP_ERROR;
        return SSH_ERROR;
    }

    /* Check if we arrived at end of file */
    if (scp->processed == scp->filelen) {
        scp->processed = scp->filelen = 0;
        ssh_channel_write(scp->channel, "", 1);
        code = ssh_scp_response(scp, NULL);
        if (code == 0) {
            scp->state = SSH_SCP_READ_INITED;
            return rc;
        }
        if (code == 1) {
            scp->state = SSH_SCP_READ_INITED;
            return SSH_ERROR;
        }
        scp->state = SSH_SCP_ERROR;
        return SSH_ERROR;
    }

    return rc;
}

Project: libssh

int ssh_scp_accept_request(ssh_scp scp)
{
    char buffer[] = {0x00};
    int rc;
    if (scp == NULL) {
        return SSH_ERROR;
    }

    if (scp->state != SSH_SCP_READ_REQUESTED) {
        ssh_set_error(scp->session, SSH_FATAL,
                      "ssh_scp_deny_request called under invalid state");
        return SSH_ERROR;
    }

    rc = ssh_channel_write(scp->channel, buffer, 1);
    if (rc == SSH_ERROR) {
        return SSH_ERROR;
    }

    if (scp->request_type == SSH_SCP_REQUEST_NEWFILE) {
        scp->state = SSH_SCP_READ_READING;
    } else {
        scp->state = SSH_SCP_READ_INITED;
    }

    return SSH_OK;
}

Project: libssh

int ssh_scp_close(ssh_scp scp)
{
    char buffer[128] = {0};
    int rc;

    if (scp == NULL) {
        return SSH_ERROR;
    }

    if (scp->channel != NULL) {
        if (ssh_channel_send_eof(scp->channel) == SSH_ERROR) {
            scp->state = SSH_SCP_ERROR;
            return SSH_ERROR;
        }
        /* avoid situations where data are buffered and
         * not yet stored on disk. This can happen if the close is sent
         * before we got the EOF back
         */
        while (!ssh_channel_is_eof(scp->channel)) {
            rc = ssh_channel_read(scp->channel, buffer, sizeof(buffer), 0);
            if (rc == SSH_ERROR || rc == 0) {
                break;
            }
        }

        if (ssh_channel_close(scp->channel) == SSH_ERROR) {
            scp->state = SSH_SCP_ERROR;
            return SSH_ERROR;
        }

        ssh_channel_free(scp->channel);
        scp->channel = NULL;
    }

    scp->state = SSH_SCP_NEW;
    return SSH_OK;
}

Project: libssh

int ssh_scp_request_get_permissions(ssh_scp scp)
{
    if (scp == NULL) {
        return -1;
    }

    return scp->request_mode;
}

Project: libssh

const char *ssh_scp_request_get_warning(ssh_scp scp)
{
    if (scp == NULL) {
        return NULL;
    }

    return scp->warning;
}

Project: libssh

void ssh_scp_free(ssh_scp scp)
{
    if (scp == NULL) {
        return;
    }

    if (scp->state != SSH_SCP_NEW) {
        ssh_scp_close(scp);
    }

    if (scp->channel) {
        ssh_channel_free(scp->channel);
    }

    SAFE_FREE(scp->location);
    SAFE_FREE(scp->request_name);
    SAFE_FREE(scp->warning);
    SAFE_FREE(scp);
}

Project: libssh

int ssh_scp_pull_request(ssh_scp scp)
{
    char buffer[MAX_BUF_SIZE] = {0};
    char *mode = NULL;
    char *p, *tmp;
    uint64_t size;
    char *name = NULL;
    int rc;

    if (scp == NULL) {
        return SSH_ERROR;
    }

    if (scp->state != SSH_SCP_READ_INITED) {
        ssh_set_error(scp->session, SSH_FATAL,
                      "ssh_scp_pull_request called under invalid state");
        return SSH_ERROR;
    }

    rc = ssh_scp_read_string(scp, buffer, sizeof(buffer));
    if (rc == SSH_ERROR) {
        if (ssh_channel_is_eof(scp->channel)) {
            scp->state = SSH_SCP_TERMINATED;
            return SSH_SCP_REQUEST_EOF;
        }
        return rc;
    }

    p = strchr(buffer, '\n');
    if (p != NULL) {
        *p = '\0';
    }

    SSH_LOG(SSH_LOG_PROTOCOL, "Received SCP request: '%s'", buffer);
    switch(buffer[0]) {
    case 'C':
        /* File */
    case 'D':
        /* Directory */
        p = strchr(buffer, ' ');
        if (p == NULL) {
            goto error;
        }
        *p = '\0';
        p++;
        //mode = strdup(&buffer[1]);
        scp->request_mode = ssh_scp_integer_mode(&buffer[1]);
        tmp = p;
        p = strchr(p, ' ');
        if (p == NULL) {
            goto error;
        }
        *p = 0;
        size = strtoull(tmp, NULL, 10);
        p++;
        name = strdup(p);
        SAFE_FREE(scp->request_name);
        scp->request_name = name;
        if (buffer[0] == 'C') {
            scp->filelen = size;
            scp->request_type = SSH_SCP_REQUEST_NEWFILE;
        } else {
            scp->filelen = '0';
            scp->request_type = SSH_SCP_REQUEST_NEWDIR;
        }
        scp->state = SSH_SCP_READ_REQUESTED;
        scp->processed = 0;
        return scp->request_type;
        break;
    case 'E':
        scp->request_type = SSH_SCP_REQUEST_ENDDIR;
        ssh_channel_write(scp->channel, "", 1);
        return scp->request_type;
    case 0x1:
        ssh_set_error(scp->session, SSH_REQUEST_DENIED,
                      "SCP: Warning: %s", &buffer[1]);
        scp->request_type = SSH_SCP_REQUEST_WARNING;
        SAFE_FREE(scp->warning);
        scp->warning = strdup(&buffer[1]);
        return scp->request_type;
    case 0x2:
        ssh_set_error(scp->session, SSH_FATAL,
                      "SCP: Error: %s", &buffer[1]);
        return SSH_ERROR;
    case 'T':
        /* Timestamp */
    default:
        ssh_set_error(scp->session, SSH_FATAL,
                      "Unhandled message: (%d)%s", buffer[0], buffer);
        return SSH_ERROR;
    }

    /* a parsing error occured */
error:
    SAFE_FREE(name);
    SAFE_FREE(mode);
    ssh_set_error(scp->session, SSH_FATAL,
                  "Parsing error while parsing message: %s", buffer);
    return SSH_ERROR;
}

Project: libssh

size_t ssh_scp_request_get_size(ssh_scp scp)
{
    if (scp == NULL) {
        return 0;
    }
    return (size_t)scp->filelen;
}

Project: libssh

int ssh_scp_deny_request(ssh_scp scp, const char *reason)
{
    char buffer[MAX_BUF_SIZE] = {0};
    int rc;

    if (scp == NULL) {
        return SSH_ERROR;
    }

    if (scp->state != SSH_SCP_READ_REQUESTED) {
        ssh_set_error(scp->session, SSH_FATAL,
                      "ssh_scp_deny_request called under invalid state");
        return SSH_ERROR;
    }

    snprintf(buffer, sizeof(buffer), "%c%s\n", 2, reason);
    rc = ssh_channel_write(scp->channel, buffer, strlen(buffer));
    if (rc == SSH_ERROR) {
        return SSH_ERROR;
    }

    else {
        scp->state = SSH_SCP_READ_INITED;
        return SSH_OK;
    }
}

Project: libssh

char *ssh_scp_string_mode(int mode)
{
    char buffer[16] = {0};
    snprintf(buffer, sizeof(buffer), "%.4o", mode);
    return strdup(buffer);
}

Project: libssh

const char *ssh_scp_request_get_filename(ssh_scp scp)
{
    if (scp == NULL) {
        return NULL;
    }

    return scp->request_name;
}

Project: libssh

int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len)
{
    size_t read = 0;
    int err = SSH_OK;

    if (scp == NULL) {
        return SSH_ERROR;
    }

    while (read < len - 1) {
        err = ssh_channel_read(scp->channel, &buffer[read], 1, 0);
        if (err == SSH_ERROR) {
            break;
        }

        if (err == 0) {
            ssh_set_error(scp->session, SSH_FATAL,
                          "End of file while reading string");
            err = SSH_ERROR;
            break;
        }

        read++;
        if (buffer[read - 1] == '\n') {
            break;
        }
    }

    buffer[read] = 0;
    return err;
}

Project: libssh

int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode)
{
    return ssh_scp_push_file64(scp, filename, (uint64_t) size, mode);
}

Project: libssh

uint64_t ssh_scp_request_get_size64(ssh_scp scp)
{
    if (scp == NULL) {
        return 0;
    }
    return scp->filelen;
}