Commit message: Avoid a crash (double-free) when SSH connection fails
CVE: CVE-2017-18594
CWE: CWE-415
Vulnerability description: nse_libssh2.cc in Nmap 7.70 is subject to a denial of service condition due to a double free when an SSH connection fails, as demonstrated by a leading \n character to ssh-brute.nse or ssh-auth-methods.nse.

Project: nmap
File: nse_libssh2.cc

__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) {
    int count;
    va_list ap;

    va_start(ap, format);
    count = c99_vsnprintf(outBuf, size, format, ap);
    va_end(ap);

    return count;
}

Project: nmap
File: nse_libssh2.cc

__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) {
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}

Project: nmap

static int channel_close (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((rc = libssh2_channel_close(*channel)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_close);
    }
    if (rc != 0)
        return luaL_error(L, "Error closing channel");;

    return 0;
}

Project: nmap

static int channel_exec (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);
    const char *cmd = luaL_checkstring(L, 3);

    while ((rc = libssh2_channel_exec(*channel, cmd)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_exec);
    }
    if (rc != 0)
        return luaL_error(L, "Error executing command");

   return 0;
}

Project: nmap
File: nse_libssh2.cc

static int channel_read (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    char buf[2048];
    size_t buflen = 2048;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((rc = libssh2_channel_read(*channel, buf, buflen)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_read);
    }

    if (rc > 0) {
        lua_pushlstring(L, buf, rc);
        return 1;
    }
    else if (rc < 0)
        return luaL_error(L, "Reading from channel");

    lua_pushnil(L);

    return 1;
}

Project: nmap

static int channel_send_eof(lua_State *L, int status, lua_KContext ctx) {
    int rc;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((rc = libssh2_channel_send_eof(*channel)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_send_eof);
    }
    if (rc != 0)
        return luaL_error(L, "Error sending EOF");

    return 0;
}

Project: nmap
File: nse_libssh2.cc

static int channel_write (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    const char *buf;
    size_t buflen = 0;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    if (lua_isstring(L, 3))
        buf = lua_tolstring(L, 3, &buflen);
    else
        return luaL_error(L, "Invalid buffer");

    while ((rc = libssh2_channel_write(*channel, buf, buflen)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_write);
    }

    if (rc < 0)
        return luaL_error(L, "Writing to channel");

    lua_pushinteger(L, rc);
    return 1;
}

Project: nmap

static int filter (lua_State *L) {
    int rc;
    char data[4096];
    struct ssh_userdata *sshu = NULL;

    sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");

    lua_getuservalue(L, 1);
    lua_getfield(L, -1, "sock");
    lua_replace(L, -2);

#ifdef WIN32
    rc = recv(sshu->sp[1], data, sizeof(data), 0);

    if (WSAGetLastError() == WSAEWOULDBLOCK)
        rc = 0;
#else
    rc = read(sshu->sp[1], data, sizeof(data));
#endif

    if (rc > 0) {
        lua_getfield(L, -1, "send");
        lua_insert(L, -2); /* swap */
        lua_pushlstring(L, data, rc);

        assert(lua_status(L) == LUA_OK);
        lua_callk(L, 2, 2, 0, finish_send);

        return finish_send(L,0,0);
    }
    else if (rc == -1 && errno != EAGAIN)
        return luaL_error(L, "%s", strerror(errno));

    lua_getfield(L, -1, "receive");
    lua_insert(L, -2); /* swap */

    assert(lua_status(L) == LUA_OK);
    lua_callk(L, 1, 2, 0, finish_read);

    return finish_read(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int finish_open_channel (lua_State *L, int status, lua_KContext ctx) {
    ssh_userdata *state = (ssh_userdata *)lua_touserdata(L, 1);
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((*channel = libssh2_channel_open_session(state->session)) == NULL
    && libssh2_session_last_errno(state->session) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, finish_open_channel);
    }
    if (channel == NULL)
        return luaL_error(L, "Opening channel");

    return setup_channel(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int finish_read (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    struct ssh_userdata *sshu = NULL;

    sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");

    if (lua_toboolean(L, -2)) {
        size_t n = 0;
        size_t l = 0;
        lua_getuservalue(L, 1);
        lua_getfield(L, -1, "sp_buff");
        lua_pushvalue(L, 3);
        lua_concat(L, 2);
        const char *data = lua_tolstring(L, -1, &l);
        lua_pushliteral(L, "");
        lua_setfield(L, 4, "sp_buff");

        while (n < l) {
#ifdef WIN32
            rc = send(sshu->sp[1], data + n, l - n, 0);
#else
            rc = write(sshu->sp[1], data + n, l - n);
#endif
            if (rc == -1 && errno != EAGAIN) {
                luaL_error(L, "Writing to socket pair: %s", strerror(errno));
            }
            else if (rc == -1 && errno == EAGAIN) {
                lua_pushlstring(L, data + n, l - n);
                lua_setfield(L, 4, "sp_buff");
                break;
            }
            else {
                n += rc;
            }
        }
        return 0;
    }
    else {
        return lua_error(L); /* uses idx 6 */
    }
}

Project: nmap
File: nse_libssh2.cc

static int finish_send (lua_State *L, int status, lua_KContext ctx) {
    if (lua_toboolean(L, -2))
        return 0;
    else
        return lua_error(L); /* uses idx 6 */
}

Project: nmap

static int gc (lua_State *L) {
    struct ssh_userdata *sshu = NULL;

    sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");
    if (!sshu) { return 0; }
    if (sshu) {

        if (sshu->session != NULL) {
            if (libssh2_session_free(sshu->session) < 0) {
            }
            sshu->session = NULL;
        }
    }

#ifdef WIN32
    closesocket(sshu->sp[0]);
    closesocket(sshu->sp[1]);
#else
    close(sshu->sp[0]);
    close(sshu->sp[1]);
#endif

    return 0;
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_close (lua_State *L) {
    return channel_close(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_eof(lua_State *L) {
    int result;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 1);

    result = libssh2_channel_eof(*channel);
    if (result >= 0)
        lua_pushboolean(L, result);
    else
        return luaL_error(L, "Error checking for EOF");

    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_exec (lua_State *L) {
    return channel_exec(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_read (lua_State *L) {
    return channel_read(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_read_stderr(lua_State *L) {
    int rc;
    char buf[2048];
    size_t buflen = 2048;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((rc = libssh2_channel_read_stderr(*channel, buf, buflen)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, channel_read);
    }

    if (rc > 0) {
        lua_pushlstring(L, buf, rc);
        return 1;
    }
    else if (rc < 0)
        return luaL_error(L, "Reading from channel");

    lua_pushnil(L);
    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int l_channel_send_eof(lua_State *L) {
    return channel_send_eof(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_hostkey_hash (lua_State *L) {
    luaL_Buffer B;
    static int hash_option[] = { LIBSSH2_HOSTKEY_HASH_MD5, LIBSSH2_HOSTKEY_HASH_SHA1 };
    static int hash_length[] = { 16, 20 };
    static const char *hashes[] = { "md5", "sha1", NULL };
    int type = luaL_checkoption(L, 2, "sha1", hashes);
    struct ssh_userdata *state = NULL;
    const unsigned char *hash = NULL;

    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");
    hash = (const unsigned char *) libssh2_hostkey_hash(state->session, hash_option[type]);

    if (hash == NULL)
        return nseU_safeerror(L, "could not get hostkey hash");

    luaL_buffinit(L, &B);
    for (int i = 0; i < hash_length[type]; i++) {
        char byte[3]; /* with space for NULL */
        snprintf(byte, sizeof(byte), "%02X", (unsigned int)hash[i]);
        if (i)
            luaL_addchar(&B, ':');
        luaL_addlstring(&B, byte, 2);
    }
    luaL_pushresult(&B);

    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int l_open_channel (lua_State *L) {
    ssh_userdata *state = (ssh_userdata *)lua_touserdata(L, 1);
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **)lua_newuserdata(L, sizeof(LIBSSH2_CHANNEL *));

    while ((*channel = libssh2_channel_open_session(state->session)) == NULL
    && libssh2_session_last_errno(state->session) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, finish_open_channel);
    }

    return l_setup_channel(L);
}

Project: nmap
File: nse_libssh2.cc

static int l_publickey_canauth (lua_State *L) {
    return publickey_canauth(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_read_publickey (lua_State *L) {
    FILE *fd;
    char c;
    const char* publickeyfile = luaL_checkstring(L, 1);
    luaL_Buffer publickey_data;

    fd = fopen(publickeyfile, "r");
    if (!fd)
        return luaL_error(L, "Error reading file");

    luaL_buffinit(L, &publickey_data);
    while (fread(&c, 1, 1, fd) && c!= '\r' && c != '\n' && c != ' ') {
        continue;
    }
    while (fread(&c, 1, 1, fd) && c!= '\r' && c != '\n' && c != ' ') {
        luaL_addchar(&publickey_data, c);
    }
    fclose(fd);

    lua_getglobal(L, "require");
    lua_pushstring(L, "base64");
    lua_call(L, 1, 1);
    lua_getfield(L, -1, "dec");

    luaL_pushresult(&publickey_data);
    lua_call(L, 1, 1);

    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int l_session_close (lua_State *L) {
    return session_close(L, 0, 0);
}

Project: nmap

static int l_session_open (lua_State *L) {
    int rc;
    ssh_userdata *state = NULL;

    luaL_checkinteger(L, 2);
    lua_settop(L, 2);

    state = (ssh_userdata *)lua_newuserdata(L, sizeof(ssh_userdata)); /* index 3 */

    assert(lua_gettop(L) == 3);
    state->session = NULL;
    state->sp[0] = -1;
    state->sp[1] = -1;
    lua_pushvalue(L, lua_upvalueindex(1)); /* metatable */
    lua_setmetatable(L, 3);

    lua_newtable(L);
    lua_setuservalue(L, 3);
    lua_getuservalue(L, 3); /* index 4 - a table associated with userdata*/
    assert(lua_gettop(L) == 4);

    state->session = libssh2_session_init();

    if (state->session == NULL) {
        return nseU_safeerror(L, "trying to initiate session");
    }

    libssh2_session_set_blocking(state->session, 0);

    if (make_socketpair(state->sp, 1) == -1)
        return nseU_safeerror(L, "trying to create socketpair");

#ifdef WIN32
    unsigned long s_mode = 1; // non-blocking

    rc = ioctlsocket(state->sp[1], FIONBIO, (unsigned long *)&s_mode);
    if (rc != NO_ERROR)
        return nseU_safeerror(L, "%s", strerror(errno));
#else
    rc = fcntl(state->sp[1], F_GETFD);
    if (rc == -1)
        return nseU_safeerror(L, "%s", strerror(errno));

    rc |= O_NONBLOCK;
    rc = fcntl(state->sp[1], F_SETFL, rc);
    if (rc == -1)
        return nseU_safeerror(L, "%s", strerror(errno));
#endif

    lua_getglobal(L, "nmap");
    lua_getfield(L, -1, "new_socket");
    lua_replace(L, -2);
    lua_call(L, 0, 1);
    lua_setfield(L, 4, "sock");
    lua_pushliteral(L, "");
    lua_setfield(L, 4, "sp_buff");
    assert(lua_gettop(L) == 4);

    lua_getfield(L, 4, "sock");
    lua_getfield(L, -1, "connect");
    lua_insert(L, -2); /* swap */
    lua_pushvalue(L, 1);
    lua_pushvalue(L, 2);
    lua_callk(L, 3, 2, 3, finish_session_open);
    return finish_session_open(L,0,0);
}

Project: nmap
File: nse_libssh2.cc

static int l_set_timeout(lua_State *L) {
    long timeout = luaL_checkinteger(L, 2);
    struct ssh_userdata *state = NULL;
    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");

    libssh2_session_set_timeout(state->session, timeout);

    return 0;
}

Project: nmap
File: nse_libssh2.cc

static int l_setup_channel (lua_State *L) {
    return setup_channel(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

 static int l_userauth_list (lua_State *L) {
     return userauth_list(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_userauth_password (lua_State *L) {
    return userauth_password(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

static int l_userauth_publickey (lua_State *L) {
    return userauth_publickey(L, 0, 0);
}

Project: nmap
File: nse_libssh2.cc

int luaopen_libssh2 (lua_State *L) {
    lua_settop(L, 0); /* clear the stack */

    luaL_newlibtable(L, libssh2);

    lua_newtable(L); /* ssh2 session metatable */
    lua_pushvalue(L, -1);
    lua_pushcclosure(L, gc, 1);
    lua_setfield(L, -2, "__gc");
    lua_pushvalue(L, -1);
    lua_pushcclosure(L, filter, 1);
    lua_setfield(L, -2, "filter");

    luaL_setfuncs(L, libssh2, 1);

    static bool libssh2_initialized = false;
    if (!libssh2_initialized && (libssh2_init(0) != 0))
        luaL_error(L, "unable to open libssh2");
    libssh2_initialized = true;

    return 1;
}

Project: nmap
File: nse_libssh2.cc

int make_socketpair (int socks[2], int dummy) {
    if (socks == 0) {
        errno = EINVAL;
        return -1;
    }

    dummy = socketpair(AF_UNIX, SOCK_STREAM, 0, socks);

    if (dummy) {
        socks[0] = socks[1] = -1;
    }

    return dummy;
}

Project: nmap
File: nse_libssh2.cc

static int publickey_canauth (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    int errlen;
    char *errmsg;
    const char *username;
    unsigned const char *publickey_data;
    size_t len = 0;
    struct ssh_userdata *state;

    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");
    username = luaL_checkstring(L, 2);

    if (lua_isstring(L, 3))
        publickey_data = (unsigned const char*)lua_tolstring(L, 3, &len);
    else
        return luaL_error(L, "Invalid public key");

    while ((rc = libssh2_userauth_publickey(state->session,
        username, publickey_data, len, &publickey_canauth_cb, NULL)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);

        assert(lua_status(L) == LUA_OK);
        lua_callk(L, 1, 0, 0, publickey_canauth);
    }

    libssh2_session_last_error(state->session, &errmsg, &errlen, 0);

    if (rc == LIBSSH2_ERROR_ALLOC || rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED)
        lua_pushboolean(L, 1); //Username/PublicKey combination invalid
    else if (rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
        lua_pushboolean(L, 0);
    else
        return luaL_error(L, "Invalid Publickey");

    return 1;
}

Project: nmap

static int setup_channel(lua_State *L, int status, lua_KContext ctx) {
    int rc;
    LIBSSH2_CHANNEL **channel = (LIBSSH2_CHANNEL **) lua_touserdata(L, 2);

    while ((rc = libssh2_channel_request_pty(*channel, "vanilla")) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);
        lua_callk(L, 1, 0, 0, setup_channel);
    }
    if (rc != 0)
        return luaL_error(L, "Requesting pty");

    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int ssh_error (lua_State *L, LIBSSH2_SESSION *session, const char *msg) {
    char *errmsg;
    libssh2_session_last_error(session, &errmsg, NULL, 0);

    return nseU_safeerror(L, "%s: %s", msg, errmsg);
}

Project: nmap

static int userauth_list (lua_State *L, int status, lua_KContext ctx) {
    char *auth_list = NULL;
    struct ssh_userdata *state = NULL;
    const char *username = luaL_checkstring(L, 2);

    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");
    assert(state->session != NULL);

    while ((auth_list = libssh2_userauth_list(state->session, username, lua_rawlen(L, 2))) == NULL
        && libssh2_session_last_errno(state->session) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);

        assert(lua_status(L) == LUA_OK);
        lua_callk(L, 1, 0, 0, userauth_list);
    }

    if (auth_list) {
        const char *auth = strtok(auth_list, ",");
        lua_newtable(L);
        do {
            lua_pushstring(L, auth);
            lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
        }
        while ((auth = strtok(NULL, ",")));

    }
    else if (libssh2_userauth_authenticated(state->session)) {
        lua_pushliteral(L, "none_auth");
    }
    else {
        return ssh_error(L, state->session, "userauth_list");
    }

    return 1;
 }

Project: nmap
File: nse_libssh2.cc

static int userauth_password (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    const char *username, *password;
    struct ssh_userdata *state;

    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");
    username = luaL_checkstring(L, 2);
    password = luaL_checkstring(L, 3);

    assert(state->session != NULL);
    while ((rc = libssh2_userauth_password(state->session,
        username, password)) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);

        assert(lua_status(L) == LUA_OK);
        lua_callk(L, 1, 0, 0, userauth_password);
    }

    if (rc == 0)
        lua_pushboolean(L, 1);
    else
        lua_pushboolean(L, 0);

    return 1;
}

Project: nmap
File: nse_libssh2.cc

static int userauth_publickey (lua_State *L, int status, lua_KContext ctx) {
    int rc;
    const char *username, *private_key_file, *passphrase, *public_key_file;
    struct ssh_userdata *state = NULL;
    state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2");

    username = luaL_checkstring(L, 2);
    private_key_file = luaL_checkstring(L, 3);

    if (lua_isstring(L, 4))
        passphrase = lua_tostring(L, 4);
    else
        passphrase = NULL;

    if (lua_isstring(L, 5))
        public_key_file = lua_tostring(L, 5);
    else
        public_key_file = NULL;

    while ((rc = libssh2_userauth_publickey_fromfile(
        state->session, username, public_key_file, private_key_file, passphrase
        )) == LIBSSH2_ERROR_EAGAIN) {
        luaL_getmetafield(L, 1, "filter");
        lua_pushvalue(L, 1);

        assert(lua_status(L) == LUA_OK);
        lua_callk(L, 1, 0, 0, userauth_publickey);
    }

    if (rc == 0)
        lua_pushboolean(L, 1);
    else
        lua_pushboolean(L, 0);

    return 1;
}