Test 11: torture_knownhosts_parsing failed on Apple silicon mac
Detailed output for the failed test
Test project /Users/jundaai/Developer/GitLab/libssh-mirror/build
Start 11: torture_knownhosts_parsing
1/1 Test #11: torture_knownhosts_parsing .......***Failed 0.02 sec
[==========] Running 17 test(s).
...
[ RUN ] torture_knownhosts_parse_line_hashed_ed25519
[ ERROR ] --- 0xfffffffffffffffe != 0
[ LINE ] --- /Users/jundaai/Developer/GitLab/libssh-mirror/tests/unittests/torture_knownhosts_parsing.c:320: error: Failure!
[ FAILED ] torture_knownhosts_parse_line_hashed_ed25519
...
[==========] 17 test(s) run.
[ PASSED ] 16 test(s).
[ FAILED ] 1 test(s), listed below:
[ FAILED ] torture_knownhosts_parse_line_hashed_ed25519
1 FAILED TEST(S)
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.02 sec
The following tests FAILED:
11 - torture_knownhosts_parsing (Failed)
Errors while running CTest
Environment Specifications (click to expand)
Operating System:
macOS Monterey 12.3.1
Compiler:
Apple clang version 13.1.6 (clang-1316.0.21.2.3)
Target: arm64-apple-darwin21.4.0
Thread model: posix
CMake:
cmake version 3.23.0
OpenSSL:
OpenSSL 1.1.1n 15 Mar 2022
libcrypt:
$ libgcrypt-config --version
1.10.1
zlib:
>>> zlib.ZLIB_VERSION
'1.2.11'
Attempted investigation
Assertion failed on line 319 of torture_knownhosts_parsing.c
// tests/unittests/torture_knownhosts_parsing.c
310 static void torture_knownhosts_parse_line_hashed_ed25519(void **state) {
...
316 rc = ssh_known_hosts_parse_line("localhost",
317 LOCALHOST_HASHED_ED25519,
318 &entry);
319 assert_int_equal(rc, SSH_OK); // rc is -2 (SSH_AGAIN)
// src/knownhosts.c
617 int ssh_known_hosts_parse_line(const char *hostname,
618 const char *line,
619 struct ssh_knownhosts_entry **entry)
620 {
...
626 int match = 0;
627 int rc = SSH_OK;
...
651 /* Hashed */
652 if (p[0] == '|') {
653 match = match_hashed_hostname(hostname, p); // returned match is 0
654 }
...
688 if (match == 0) {
689 rc = SSH_AGAIN; // rc is set to SSH_AGAIN and returned, causing the assertion failure
690 goto out;
691 }
// src/knownhosts.c
79 static int match_hashed_hostname(const char *host, const char *hashed_host)
80 {
...
81 ssh_buffer hash = NULL;
82 unsigned char hashed_buf[256] = {0};
...
84 unsigned int hashed_buf_size = sizeof(hashed_buf);
...
111 hash = base64_to_bin(b64_hash);
...
116 rc = hash_hostname(host,
117 ssh_buffer_get(salt),
118 ssh_buffer_get_len(salt),
119 &hashed_buf_ptr,
120 &hashed_buf_size);
...
125 if (hashed_buf_size != ssh_buffer_get_len(hash)) { // hashed_buf_size is 0, ssh_buffer_get_len(hash) is 20
126 goto error; // error statements free up some memory and return match which is 0
127 }
// src/knownhosts.c
57 static int hash_hostname(const char *name,
58 unsigned char *salt,
59 unsigned int salt_size,
60 unsigned char **hash,
61 unsigned int *hash_size)
62 {
63 HMACCTX mac_ctx;
64
65 mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1);
...
70 hmac_update(mac_ctx, name, strlen(name));
71 hmac_final(mac_ctx, *hash, hash_size); // writes to hash and hash_size (hashed_buf_size in match_hashed_hostname)
...
// src/libcrypto.c
469 void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) // input hashmacbuf is empty buffer, len is 256
470 {
471 size_t res = 0;
472 EVP_DigestSignFinal(ctx, hashmacbuf, &res); // I suspect that this call failed, res is 0 and should be 20
473 EVP_MD_CTX_free(ctx);
474 *len = res; // *len (hash_size in hash_hostname) is 0
475 }
In OpenSSL document's description about EVP_DigestFinal
(https://www.openssl.org/docs/man3.0/man3/EVP_DigestSignFinal.html):
Unless sig is NULL EVP_DigestSignFinal() signs the data in ctx and places the signature in sig. Otherwise the maximum necessary size of the output buffer is written to the siglen parameter. If sig is not NULL then before the call the siglen parameter should contain the length of the sig buffer. If the call is successful the signature is written to sig and the amount of data written to siglen.
So after line 472 res
and later *len
are expected to hold the amount of data written to *hashmacbuf
. On a Debian 10 virtual machine which was able to pass this test, *hashmacbuf
could be successfully written, and so was *len
.
gdb output on Debian after executing line 472
> print hashmacbuf
$0 = (unsigned char *) 0xffffffffe248 "\206Fć\301M\350\211\v\243\253z\031\001\256\a\006\246\202", <incomplete sequence \303>
> print res
$1 = 20
> print *len
$2 = 20
On the M1 MacBook which failed this test and also the context of this issue, *hashmacbuf
was still empty after line 472, and accordingly *len
was 0.
lldb output on M1 mac after executing line 472
> p hashmacbuf
(unsigned char *) $0 = 0x000000016fdfe258 ""
> p res
(size_t) $1 = 0
> p *len
(unsigned int) $2 = 0
Both the Debian VM that passed and the macOS that failed use OpenSSL 1.1.1n 15 Mar 2022
.