1
0
mirror of https://github.com/libssh2/libssh2.git synced 2026-10-24 03:48:58 +03:00
Files
libssh2/tests/test_simple.c
syed abdul khaliq 7f28c290d3 wincng: validate Diffie-Hellman public value in ssh2_dh_secret()
ssh2_dh_secret() in the Windows CNG backend uses the server's
Diffie-Hellman public value f without the range check the other four
backends apply through ssh2_dh_validate(), so a malicious server can
send f of 0, 1 or p-1 and push the shared secret into a trivial subgroup
that openssl, libgcrypt, mbedtls and os400qc3 all reject with
LIBSSH2_ERROR_PROTO. This adds a WinCNG ssh2_dh_validate() that enforces
1 < f < p-1 and the same minimum-set-bits check on the big-endian value,
and calls it at the top of ssh2_dh_secret() like the other backends do.
The shared test_ssh2_dh_validate() in tests/test_simple.c was stubbed
out on WinCNG only because the function did not exist, so it now runs
against the real implementation too.

Reported-by: yaotushaozhu on github
Ref: GHSA-m9rg-h9jv-9gvh
Follow-up to fcf830788d #2239

Closes #2645
2026-10-04 21:17:02 +02:00

407 lines
12 KiB
C

/* Copyright (C) The Written Word, Inc.
* Copyright (C) Simon Josefsson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "libssh2_priv.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> /* for atoi() */
static int test_ssh2_base64_decode(LIBSSH2_SESSION *session)
{
char *data;
size_t datalen;
const char *src = "Zm5vcmQ=";
int ret;
ret = ssh2_base64_decode(session, &data, &datalen, src, strlen(src));
if(ret)
return ret;
if(datalen != 5 || strcmp(data, "fnord")) {
fprintf(stderr, "ssh2_base64_decode() failed (%d, %.*s)\n",
(int)datalen, (int)datalen, data);
return 1;
}
free(data);
return 0;
}
static int test_knownhost_ipv6_case(LIBSSH2_SESSION *session,
const char *stored,
const char *checked,
int port,
int expected)
{
static const char key[] = "AAAA";
const int typemask =
LIBSSH2_KNOWNHOST_TYPE_PLAIN |
LIBSSH2_KNOWNHOST_KEYENC_BASE64 |
LIBSSH2_KNOWNHOST_KEY_SSHRSA;
LIBSSH2_KNOWNHOSTS *hosts;
int rc;
hosts = libssh2_knownhost_init(session);
if(!hosts) {
fprintf(stderr, "libssh2_knownhost_init() failed\n");
return 1;
}
rc = libssh2_knownhost_addc(hosts, stored, NULL, key, strlen(key),
NULL, 0, typemask, NULL);
if(rc) {
fprintf(stderr, "libssh2_knownhost_addc(%s) failed: %d\n",
stored, rc);
libssh2_knownhost_free(hosts);
return 1;
}
if(port >= 0)
rc = libssh2_knownhost_checkp(hosts, checked, port, key,
strlen(key), typemask, NULL);
else
rc = libssh2_knownhost_check(hosts, checked, key, strlen(key),
typemask, NULL);
libssh2_knownhost_free(hosts);
if(rc != expected) {
fprintf(stderr,
"knownhost IPv6: stored=%s checked=%s port=%d "
"expected=%d got=%d\n",
stored, checked, port, expected, rc);
return 1;
}
return 0;
}
static int test_knownhost_ipv6(LIBSSH2_SESSION *session)
{
int err = 0;
err |= test_knownhost_ipv6_case(
session,
"fd5f:4268:2387:97c1:0000:0000:0000:0002",
"fd5f:4268:2387:97c1::2",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
err |= test_knownhost_ipv6_case(
session,
"fd5f:4268:2387:97c1::2",
"fd5f:4268:2387:97c1:0:0:0:2",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
err |= test_knownhost_ipv6_case(
session,
"0:0:0:0:0:ffff:c000:0201",
"::ffff:192.0.2.1",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
err |= test_knownhost_ipv6_case(
session,
"[fd5f:4268:2387:97c1:0000:0000:0000:0002]:2222",
"fd5f:4268:2387:97c1::2",
2222, LIBSSH2_KNOWNHOST_CHECK_MATCH);
err |= test_knownhost_ipv6_case(
session,
"[fd5f:4268:2387:97c1:0000:0000:0000:0002]:2222",
"fd5f:4268:2387:97c1::2",
22, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
err |= test_knownhost_ipv6_case(
session,
"fd5f:4268:2387:97c1::2",
"fd5f:4268:2387:97c1::3",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
err |= test_knownhost_ipv6_case(
session,
"fd5f:4268:2387:97c1::2",
"fd5f:4268:2387:97c1::1::2",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
err |= test_knownhost_ipv6_case(
session,
"example.com",
"example.com",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
err |= test_knownhost_ipv6_case(
session,
"example.com",
"EXAMPLE.COM",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
/* Hexadecimal digits are case-insensitive. */
err |= test_knownhost_ipv6_case(
session,
"2001:0DB8:0000:0000:0000:0000:0000:0001",
"2001:db8::1",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
/* Compression may represent the complete address. */
err |= test_knownhost_ipv6_case(
session,
"0:0:0:0:0:0:0:0",
"::",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
/* Compression at the end must expand to the remaining words. */
err |= test_knownhost_ipv6_case(
session,
"2001:db8:1:2:3:4:0:0",
"2001:db8:1:2:3:4::",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
/* More than eight words is not a valid IPv6 literal. */
err |= test_knownhost_ipv6_case(
session,
"2001:db8::1",
"1:2:3:4:5:6:7:8:9",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
/* An embedded IPv4 address must contain valid octets. */
err |= test_knownhost_ipv6_case(
session,
"::ffff:192.0.2.1",
"::ffff:192.0.2.256",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
/* Embedded IPv4 octets with leading zeros are not valid. */
err |= test_knownhost_ipv6_case(
session,
"::ffff:192.168.001.1",
"::ffff:192.168.1.1",
-1, LIBSSH2_KNOWNHOST_CHECK_NOTFOUND);
/* Identical malformed names retain the historical exact-string match. */
err |= test_knownhost_ipv6_case(
session,
"::ffff:192.168.001.1",
"::ffff:192.168.001.1",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
/*
* Preserve the historical exact-string behavior even when a plain host
* containing colons is not a valid IPv6 literal.
*/
err |= test_knownhost_ipv6_case(
session,
"fd5f::1::2",
"fd5f::1::2",
-1, LIBSSH2_KNOWNHOST_CHECK_MATCH);
return err > 0;
}
static int test_ssh2_dh_validate(void)
{
struct tbn {
const char *f; const char *p; int expected;
};
static const struct tbn tests[] = {
{ "2", "10", -3 },
{ "1", "10", -1 },
{ "0", "10", -1 },
{ "9", "10", -2 }, /* f=9; p=10 => p-2=8, f>8 */
{ "6", "20", -3 }, /* f=6 (110); p=20 => p-2=18, 6<=18 */
{ "27", "100", 0 }, /* f=27 (11011); p=100 => p-2=98, 27<=98 */
{ "240", "242", 0 }, /* f=p-2, f=240 (11110000); p=f+2=242 */
};
size_t i;
int err = 0;
for(i = 0; i < SSH2_ARRAYSIZE(tests); i++) {
struct tbn t = tests[i];
int got;
ssh2_bn *f = ssh2_bn_init();
ssh2_bn *p = ssh2_bn_init();
if(!f || !p ||
ssh2_bn_set_word(f, (uint32_t)atoi(t.f)) ||
ssh2_bn_set_word(p, (uint32_t)atoi(t.p)))
got = -9;
else
got = ssh2_dh_validate(f, p);
ssh2_bn_free(f);
ssh2_bn_free(p);
if(got != t.expected) {
fprintf(stderr,
"ssh2_dh_validate/%lu: f=%s p=%s: expected %d got %d\n",
(unsigned long)i, t.f, t.p, t.expected, got);
err++;
}
}
return err > 0;
}
static int test_ssh2_bn_from_bin(void)
{
static const struct {
unsigned char input[5];
size_t length;
size_t leading;
} tests[] = {
{ { 1 }, 1, 0 },
{ { 0x7f }, 1, 0 },
{ { 0x80 }, 1, 0 },
{ { 0xff, 0x42 }, 2, 0 },
{ { 0, 1 }, 2, 1 },
{ { 0, 0x80 }, 2, 1 },
{ { 0, 0, 0x80, 0x42 }, 4, 2 },
{ { 0, 0, 0, 0, 1 }, 5, 4 },
};
size_t i;
int rc = 0;
for(i = 0; i < SSH2_ARRAYSIZE(tests); i++) {
unsigned char actual[5] = { 0 };
size_t length = tests[i].length - tests[i].leading;
ssh2_bn *bn = NULL;
if(ssh2_bn_from_bin(&bn, tests[i].input, tests[i].length) ||
ssh2_bn_bytes(bn) != length ||
ssh2_bn_to_bin(bn, actual) ||
memcmp(actual, tests[i].input + tests[i].leading, length)) {
fprintf(stderr, "ssh2_bn_from_bin case %lu failed\n",
(unsigned long)i);
rc = 1;
}
ssh2_bn_free(bn);
}
return rc;
}
/* Return codes match scp.c (SCP_C_FIELDS_*). */
static int test_ssh2_scp_parse_c_fields(void)
{
long mode = -1;
libssh2_int64_t size = -1;
char long_line[SSH2_SCP_RESPONSE_BUFLEN];
int prefix_len;
int prc;
int err = 0;
/* Normal complete line with short name */
prc = ssh2_scp_parse_c_fields("C0644 123 shortname\n", 20, &mode, &size);
if(prc || mode != 420L || size != 123) { /* 0644 octal == 420 */
fprintf(stderr, "scp_parse short: prc=%d mode=%ld size=%lu\n",
prc, mode, (unsigned long)size);
err++;
}
/* Fields complete without trailing newline (name unfinished) */
mode = -1;
size = -1;
prc = ssh2_scp_parse_c_fields("C0755 42 ", 9, &mode, &size);
if(prc || mode != 493L || size != 42) { /* 0755 octal == 493 */
fprintf(stderr, "scp_parse partial-name: prc=%d mode=%ld size=%lu\n",
prc, mode, (unsigned long)size);
err++;
}
/* Incomplete size digits still growing */
prc = ssh2_scp_parse_c_fields("C0644 12", 8, &mode, &size);
if(prc != 1) {
fprintf(stderr, "scp_parse incomplete size: prc=%d (want 1)\n", prc);
err++;
}
/*
* Fixed buffer full of "Cmode size " + long name without newline. Mode and
* size must still parse so scp_recv can drain the unused basename.
*/
prefix_len = snprintf(long_line, sizeof(long_line), "C0644 99 ");
if(prefix_len < 0 || (size_t)prefix_len >= sizeof(long_line)) {
fprintf(stderr, "scp_parse long-name: snprintf failed (%d)\n",
prefix_len);
return 1;
}
memset(long_line + prefix_len, 'a',
sizeof(long_line) - (size_t)prefix_len);
mode = -1;
size = -1;
prc = ssh2_scp_parse_c_fields(long_line, sizeof(long_line), &mode, &size);
if(prc || mode != 420L || size != 99) { /* 0644 octal == 420 */
fprintf(stderr,
"scp_parse long-name buffer: prc=%d mode=%ld size=%lu\n",
prc, mode, (unsigned long)size);
err++;
}
/* Malformed: bad mode */
prc = ssh2_scp_parse_c_fields("Cxyz 1 name\n", 12, &mode, &size);
if(prc != -1) {
fprintf(stderr, "scp_parse bad mode: prc=%d (want -1)\n", prc);
err++;
}
return err > 0;
}
int main(int argc, char *argv[])
{
LIBSSH2_SESSION *session;
int rc;
(void)argv;
(void)argc;
rc = libssh2_init(0);
if(rc) {
fprintf(stderr, "libssh2_init() failed: %d\n", rc);
return 1;
}
session = libssh2_session_init();
if(!session) {
fprintf(stderr, "libssh2_session_init() failed\n");
return 1;
}
rc = test_ssh2_base64_decode(session);
rc |= test_knownhost_ipv6(session);
rc |= test_ssh2_dh_validate();
rc |= test_ssh2_bn_from_bin();
rc |= test_ssh2_scp_parse_c_fields();
libssh2_session_free(session);
libssh2_exit();
return rc;
}