lol

libssh2: apply patch for CVE-2023-48795

+465
+459
pkgs/development/libraries/libssh2/CVE-2023-48795.patch
··· 1 + From d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a Mon Sep 17 00:00:00 2001 2 + From: Michael Buckley <michael@buckleyisms.com> 3 + Date: Thu, 30 Nov 2023 15:08:02 -0800 4 + Subject: [PATCH] src: add 'strict KEX' to fix CVE-2023-48795 "Terrapin Attack" 5 + 6 + Refs: 7 + https://terrapin-attack.com/ 8 + https://seclists.org/oss-sec/2023/q4/292 9 + https://osv.dev/list?ecosystem=&q=CVE-2023-48795 10 + https://github.com/advisories/GHSA-45x7-px36-x8w8 11 + https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795 12 + 13 + Fixes #1290 14 + Closes #1291 15 + --- 16 + src/kex.c | 63 +++++++++++++++++++++++------------ 17 + src/libssh2_priv.h | 18 +++++++--- 18 + src/packet.c | 83 +++++++++++++++++++++++++++++++++++++++++++--- 19 + src/packet.h | 2 +- 20 + src/session.c | 3 ++ 21 + src/transport.c | 12 ++++++- 22 + 6 files changed, 149 insertions(+), 32 deletions(-) 23 + 24 + diff --git a/src/kex.c b/src/kex.c 25 + index 8e7b7f0af3..a7b301e157 100644 26 + --- a/src/kex.c 27 + +++ b/src/kex.c 28 + @@ -3032,6 +3032,13 @@ kex_method_extension_negotiation = { 29 + 0, 30 + }; 31 + 32 + +static const LIBSSH2_KEX_METHOD 33 + +kex_method_strict_client_extension = { 34 + + "kex-strict-c-v00@openssh.com", 35 + + NULL, 36 + + 0, 37 + +}; 38 + + 39 + static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { 40 + #if LIBSSH2_ED25519 41 + &kex_method_ssh_curve25519_sha256, 42 + @@ -3050,6 +3057,7 @@ static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { 43 + &kex_method_diffie_helman_group1_sha1, 44 + &kex_method_diffie_helman_group_exchange_sha1, 45 + &kex_method_extension_negotiation, 46 + + &kex_method_strict_client_extension, 47 + NULL 48 + }; 49 + 50 + @@ -3302,13 +3310,13 @@ static int kexinit(LIBSSH2_SESSION * session) 51 + return 0; 52 + } 53 + 54 + -/* kex_agree_instr 55 + +/* _libssh2_kex_agree_instr 56 + * Kex specific variant of strstr() 57 + * Needle must be preceded by BOL or ',', and followed by ',' or EOL 58 + */ 59 + -static unsigned char * 60 + -kex_agree_instr(unsigned char *haystack, size_t haystack_len, 61 + - const unsigned char *needle, size_t needle_len) 62 + +unsigned char * 63 + +_libssh2_kex_agree_instr(unsigned char *haystack, size_t haystack_len, 64 + + const unsigned char *needle, size_t needle_len) 65 + { 66 + unsigned char *s; 67 + unsigned char *end_haystack; 68 + @@ -3393,7 +3401,7 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session, 69 + while(s && *s) { 70 + unsigned char *p = (unsigned char *) strchr((char *) s, ','); 71 + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 72 + - if(kex_agree_instr(hostkey, hostkey_len, s, method_len)) { 73 + + if(_libssh2_kex_agree_instr(hostkey, hostkey_len, s, method_len)) { 74 + const LIBSSH2_HOSTKEY_METHOD *method = 75 + (const LIBSSH2_HOSTKEY_METHOD *) 76 + kex_get_method_by_name((char *) s, method_len, 77 + @@ -3427,9 +3435,9 @@ static int kex_agree_hostkey(LIBSSH2_SESSION * session, 78 + } 79 + 80 + while(hostkeyp && (*hostkeyp) && (*hostkeyp)->name) { 81 + - s = kex_agree_instr(hostkey, hostkey_len, 82 + - (unsigned char *) (*hostkeyp)->name, 83 + - strlen((*hostkeyp)->name)); 84 + + s = _libssh2_kex_agree_instr(hostkey, hostkey_len, 85 + + (unsigned char *) (*hostkeyp)->name, 86 + + strlen((*hostkeyp)->name)); 87 + if(s) { 88 + /* So far so good, but does it suit our purposes? (Encrypting vs 89 + Signing) */ 90 + @@ -3463,6 +3471,12 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 91 + { 92 + const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods; 93 + unsigned char *s; 94 + + const unsigned char *strict = 95 + + (unsigned char *)"kex-strict-s-v00@openssh.com"; 96 + + 97 + + if(_libssh2_kex_agree_instr(kex, kex_len, strict, 28)) { 98 + + session->kex_strict = 1; 99 + + } 100 + 101 + if(session->kex_prefs) { 102 + s = (unsigned char *) session->kex_prefs; 103 + @@ -3470,7 +3484,7 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 104 + while(s && *s) { 105 + unsigned char *q, *p = (unsigned char *) strchr((char *) s, ','); 106 + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 107 + - q = kex_agree_instr(kex, kex_len, s, method_len); 108 + + q = _libssh2_kex_agree_instr(kex, kex_len, s, method_len); 109 + if(q) { 110 + const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *) 111 + kex_get_method_by_name((char *) s, method_len, 112 + @@ -3504,9 +3518,9 @@ static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex, 113 + } 114 + 115 + while(*kexp && (*kexp)->name) { 116 + - s = kex_agree_instr(kex, kex_len, 117 + - (unsigned char *) (*kexp)->name, 118 + - strlen((*kexp)->name)); 119 + + s = _libssh2_kex_agree_instr(kex, kex_len, 120 + + (unsigned char *) (*kexp)->name, 121 + + strlen((*kexp)->name)); 122 + if(s) { 123 + /* We've agreed on a key exchange method, 124 + * Can we agree on a hostkey that works with this kex? 125 + @@ -3550,7 +3564,7 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session, 126 + unsigned char *p = (unsigned char *) strchr((char *) s, ','); 127 + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 128 + 129 + - if(kex_agree_instr(crypt, crypt_len, s, method_len)) { 130 + + if(_libssh2_kex_agree_instr(crypt, crypt_len, s, method_len)) { 131 + const LIBSSH2_CRYPT_METHOD *method = 132 + (const LIBSSH2_CRYPT_METHOD *) 133 + kex_get_method_by_name((char *) s, method_len, 134 + @@ -3572,9 +3586,9 @@ static int kex_agree_crypt(LIBSSH2_SESSION * session, 135 + } 136 + 137 + while(*cryptp && (*cryptp)->name) { 138 + - s = kex_agree_instr(crypt, crypt_len, 139 + - (unsigned char *) (*cryptp)->name, 140 + - strlen((*cryptp)->name)); 141 + + s = _libssh2_kex_agree_instr(crypt, crypt_len, 142 + + (unsigned char *) (*cryptp)->name, 143 + + strlen((*cryptp)->name)); 144 + if(s) { 145 + endpoint->crypt = *cryptp; 146 + return 0; 147 + @@ -3614,7 +3628,7 @@ static int kex_agree_mac(LIBSSH2_SESSION * session, 148 + unsigned char *p = (unsigned char *) strchr((char *) s, ','); 149 + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 150 + 151 + - if(kex_agree_instr(mac, mac_len, s, method_len)) { 152 + + if(_libssh2_kex_agree_instr(mac, mac_len, s, method_len)) { 153 + const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *) 154 + kex_get_method_by_name((char *) s, method_len, 155 + (const LIBSSH2_COMMON_METHOD **) 156 + @@ -3635,8 +3649,9 @@ static int kex_agree_mac(LIBSSH2_SESSION * session, 157 + } 158 + 159 + while(*macp && (*macp)->name) { 160 + - s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name, 161 + - strlen((*macp)->name)); 162 + + s = _libssh2_kex_agree_instr(mac, mac_len, 163 + + (unsigned char *) (*macp)->name, 164 + + strlen((*macp)->name)); 165 + if(s) { 166 + endpoint->mac = *macp; 167 + return 0; 168 + @@ -3667,7 +3682,7 @@ static int kex_agree_comp(LIBSSH2_SESSION *session, 169 + unsigned char *p = (unsigned char *) strchr((char *) s, ','); 170 + size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s)); 171 + 172 + - if(kex_agree_instr(comp, comp_len, s, method_len)) { 173 + + if(_libssh2_kex_agree_instr(comp, comp_len, s, method_len)) { 174 + const LIBSSH2_COMP_METHOD *method = 175 + (const LIBSSH2_COMP_METHOD *) 176 + kex_get_method_by_name((char *) s, method_len, 177 + @@ -3689,8 +3704,9 @@ static int kex_agree_comp(LIBSSH2_SESSION *session, 178 + } 179 + 180 + while(*compp && (*compp)->name) { 181 + - s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name, 182 + - strlen((*compp)->name)); 183 + + s = _libssh2_kex_agree_instr(comp, comp_len, 184 + + (unsigned char *) (*compp)->name, 185 + + strlen((*compp)->name)); 186 + if(s) { 187 + endpoint->comp = *compp; 188 + return 0; 189 + @@ -3871,6 +3887,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 190 + session->local.kexinit = key_state->oldlocal; 191 + session->local.kexinit_len = key_state->oldlocal_len; 192 + key_state->state = libssh2_NB_state_idle; 193 + + session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 194 + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 195 + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 196 + return -1; 197 + @@ -3896,6 +3913,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 198 + session->local.kexinit = key_state->oldlocal; 199 + session->local.kexinit_len = key_state->oldlocal_len; 200 + key_state->state = libssh2_NB_state_idle; 201 + + session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 202 + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 203 + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 204 + return -1; 205 + @@ -3944,6 +3962,7 @@ _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 206 + session->remote.kexinit = NULL; 207 + } 208 + 209 + + session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 210 + session->state &= ~LIBSSH2_STATE_KEX_ACTIVE; 211 + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 212 + 213 + diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h 214 + index 7660366954..18d9ab2130 100644 215 + --- a/src/libssh2_priv.h 216 + +++ b/src/libssh2_priv.h 217 + @@ -736,6 +736,9 @@ struct _LIBSSH2_SESSION 218 + /* key signing algorithm preferences -- NULL yields server order */ 219 + char *sign_algo_prefs; 220 + 221 + + /* Whether to use the OpenSSH Strict KEX extension */ 222 + + int kex_strict; 223 + + 224 + /* (remote as source of data -- packet_read ) */ 225 + libssh2_endpoint_data remote; 226 + 227 + @@ -908,6 +911,7 @@ struct _LIBSSH2_SESSION 228 + int fullpacket_macstate; 229 + size_t fullpacket_payload_len; 230 + int fullpacket_packet_type; 231 + + uint32_t fullpacket_required_type; 232 + 233 + /* State variables used in libssh2_sftp_init() */ 234 + libssh2_nonblocking_states sftpInit_state; 235 + @@ -948,10 +952,11 @@ struct _LIBSSH2_SESSION 236 + }; 237 + 238 + /* session.state bits */ 239 + -#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000001 240 + -#define LIBSSH2_STATE_NEWKEYS 0x00000002 241 + -#define LIBSSH2_STATE_AUTHENTICATED 0x00000004 242 + -#define LIBSSH2_STATE_KEX_ACTIVE 0x00000008 243 + +#define LIBSSH2_STATE_INITIAL_KEX 0x00000001 244 + +#define LIBSSH2_STATE_EXCHANGING_KEYS 0x00000002 245 + +#define LIBSSH2_STATE_NEWKEYS 0x00000004 246 + +#define LIBSSH2_STATE_AUTHENTICATED 0x00000008 247 + +#define LIBSSH2_STATE_KEX_ACTIVE 0x00000010 248 + 249 + /* session.flag helpers */ 250 + #ifdef MSG_NOSIGNAL 251 + @@ -1182,6 +1187,11 @@ ssize_t _libssh2_send(libssh2_socket_t socket, const void *buffer, 252 + int _libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange, 253 + key_exchange_state_t * state); 254 + 255 + +unsigned char *_libssh2_kex_agree_instr(unsigned char *haystack, 256 + + size_t haystack_len, 257 + + const unsigned char *needle, 258 + + size_t needle_len); 259 + + 260 + /* Let crypt.c/hostkey.c expose their method structs */ 261 + const LIBSSH2_CRYPT_METHOD **libssh2_crypt_methods(void); 262 + const LIBSSH2_HOSTKEY_METHOD **libssh2_hostkey_methods(void); 263 + diff --git a/src/packet.c b/src/packet.c 264 + index eccb8c56a8..6da14e9fa1 100644 265 + --- a/src/packet.c 266 + +++ b/src/packet.c 267 + @@ -624,14 +624,13 @@ packet_authagent_open(LIBSSH2_SESSION * session, 268 + * layer when it has received a packet. 269 + * 270 + * The input pointer 'data' is pointing to allocated data that this function 271 + - * is asked to deal with so on failure OR success, it must be freed fine. 272 + - * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN. 273 + + * will be freed unless return the code is LIBSSH2_ERROR_EAGAIN. 274 + * 275 + * This function will always be called with 'datalen' greater than zero. 276 + */ 277 + int 278 + _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 279 + - size_t datalen, int macstate) 280 + + size_t datalen, int macstate, uint32_t seq) 281 + { 282 + int rc = 0; 283 + unsigned char *message = NULL; 284 + @@ -676,6 +675,70 @@ _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 285 + break; 286 + } 287 + 288 + + if(session->state & LIBSSH2_STATE_INITIAL_KEX) { 289 + + if(msg == SSH_MSG_KEXINIT) { 290 + + if(!session->kex_strict) { 291 + + if(datalen < 17) { 292 + + LIBSSH2_FREE(session, data); 293 + + session->packAdd_state = libssh2_NB_state_idle; 294 + + return _libssh2_error(session, 295 + + LIBSSH2_ERROR_BUFFER_TOO_SMALL, 296 + + "Data too short extracting kex"); 297 + + } 298 + + else { 299 + + const unsigned char *strict = 300 + + (unsigned char *)"kex-strict-s-v00@openssh.com"; 301 + + struct string_buf buf; 302 + + unsigned char *algs = NULL; 303 + + size_t algs_len = 0; 304 + + 305 + + buf.data = (unsigned char *)data; 306 + + buf.dataptr = buf.data; 307 + + buf.len = datalen; 308 + + buf.dataptr += 17; /* advance past type and cookie */ 309 + + 310 + + if(_libssh2_get_string(&buf, &algs, &algs_len)) { 311 + + LIBSSH2_FREE(session, data); 312 + + session->packAdd_state = libssh2_NB_state_idle; 313 + + return _libssh2_error(session, 314 + + LIBSSH2_ERROR_BUFFER_TOO_SMALL, 315 + + "Algs too short"); 316 + + } 317 + + 318 + + if(algs_len == 0 || 319 + + _libssh2_kex_agree_instr(algs, algs_len, strict, 28)) { 320 + + session->kex_strict = 1; 321 + + } 322 + + } 323 + + } 324 + + 325 + + if(session->kex_strict && seq) { 326 + + LIBSSH2_FREE(session, data); 327 + + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; 328 + + session->packAdd_state = libssh2_NB_state_idle; 329 + + libssh2_session_disconnect(session, "strict KEX violation: " 330 + + "KEXINIT was not the first packet"); 331 + + 332 + + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 333 + + "strict KEX violation: " 334 + + "KEXINIT was not the first packet"); 335 + + } 336 + + } 337 + + 338 + + if(session->kex_strict && session->fullpacket_required_type && 339 + + session->fullpacket_required_type != msg) { 340 + + LIBSSH2_FREE(session, data); 341 + + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; 342 + + session->packAdd_state = libssh2_NB_state_idle; 343 + + libssh2_session_disconnect(session, "strict KEX violation: " 344 + + "unexpected packet type"); 345 + + 346 + + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 347 + + "strict KEX violation: " 348 + + "unexpected packet type"); 349 + + } 350 + + } 351 + + 352 + if(session->packAdd_state == libssh2_NB_state_allocated) { 353 + /* A couple exceptions to the packet adding rule: */ 354 + switch(msg) { 355 + @@ -1364,6 +1427,15 @@ _libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, 356 + 357 + return 0; 358 + } 359 + + else if(session->kex_strict && 360 + + (session->state & LIBSSH2_STATE_INITIAL_KEX)) { 361 + + libssh2_session_disconnect(session, "strict KEX violation: " 362 + + "unexpected packet type"); 363 + + 364 + + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, 365 + + "strict KEX violation: " 366 + + "unexpected packet type"); 367 + + } 368 + packet = _libssh2_list_next(&packet->node); 369 + } 370 + return -1; 371 + @@ -1425,7 +1497,10 @@ _libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type, 372 + } 373 + 374 + while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) { 375 + - int ret = _libssh2_transport_read(session); 376 + + int ret; 377 + + session->fullpacket_required_type = packet_type; 378 + + ret = _libssh2_transport_read(session); 379 + + session->fullpacket_required_type = 0; 380 + if(ret == LIBSSH2_ERROR_EAGAIN) 381 + return ret; 382 + else if(ret < 0) { 383 + diff --git a/src/packet.h b/src/packet.h 384 + index 1d90b8af12..955351e5f6 100644 385 + --- a/src/packet.h 386 + +++ b/src/packet.h 387 + @@ -72,6 +72,6 @@ int _libssh2_packet_burn(LIBSSH2_SESSION * session, 388 + int _libssh2_packet_write(LIBSSH2_SESSION * session, unsigned char *data, 389 + unsigned long data_len); 390 + int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, 391 + - size_t datalen, int macstate); 392 + + size_t datalen, int macstate, uint32_t seq); 393 + 394 + #endif /* LIBSSH2_PACKET_H */ 395 + diff --git a/src/session.c b/src/session.c 396 + index 35e7929fe7..9d89ade8ec 100644 397 + --- a/src/session.c 398 + +++ b/src/session.c 399 + @@ -469,6 +469,8 @@ libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), 400 + session->abstract = abstract; 401 + session->api_timeout = 0; /* timeout-free API by default */ 402 + session->api_block_mode = 1; /* blocking API by default */ 403 + + session->state = LIBSSH2_STATE_INITIAL_KEX; 404 + + session->fullpacket_required_type = 0; 405 + session->packet_read_timeout = LIBSSH2_DEFAULT_READ_TIMEOUT; 406 + session->flag.quote_paths = 1; /* default behavior is to quote paths 407 + for the scp subsystem */ 408 + @@ -1223,6 +1225,7 @@ libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, 409 + const char *desc, const char *lang) 410 + { 411 + int rc; 412 + + session->state &= ~LIBSSH2_STATE_INITIAL_KEX; 413 + session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS; 414 + BLOCK_ADJUST(rc, session, 415 + session_disconnect(session, reason, desc, lang)); 416 + diff --git a/src/transport.c b/src/transport.c 417 + index 21be9d2b80..a8bb588a4b 100644 418 + --- a/src/transport.c 419 + +++ b/src/transport.c 420 + @@ -186,6 +186,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 421 + struct transportpacket *p = &session->packet; 422 + int rc; 423 + int compressed; 424 + + uint32_t seq = session->remote.seqno; 425 + 426 + if(session->fullpacket_state == libssh2_NB_state_idle) { 427 + session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED; 428 + @@ -317,7 +318,7 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 429 + if(session->fullpacket_state == libssh2_NB_state_created) { 430 + rc = _libssh2_packet_add(session, p->payload, 431 + session->fullpacket_payload_len, 432 + - session->fullpacket_macstate); 433 + + session->fullpacket_macstate, seq); 434 + if(rc == LIBSSH2_ERROR_EAGAIN) 435 + return rc; 436 + if(rc) { 437 + @@ -328,6 +329,11 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) 438 + 439 + session->fullpacket_state = libssh2_NB_state_idle; 440 + 441 + + if(session->kex_strict && 442 + + session->fullpacket_packet_type == SSH_MSG_NEWKEYS) { 443 + + session->remote.seqno = 0; 444 + + } 445 + + 446 + return session->fullpacket_packet_type; 447 + } 448 + 449 + @@ -1093,6 +1099,10 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, 450 + 451 + session->local.seqno++; 452 + 453 + + if(session->kex_strict && data[0] == SSH_MSG_NEWKEYS) { 454 + + session->local.seqno = 0; 455 + + } 456 + + 457 + ret = LIBSSH2_SEND(session, p->outbuf, total_length, 458 + LIBSSH2_SOCKET_SEND_FLAGS(session)); 459 + if(ret < 0)
+6
pkgs/development/libraries/libssh2/default.nix
··· 9 9 sha256 = "sha256-NzYWHkHiaTMk3rOMJs/cPv5iCdY0ukJY2xzs/2pa1GE="; 10 10 }; 11 11 12 + patches = [ 13 + # fetchpatch cannot be used due to infinite recursion 14 + # https://github.com/libssh2/libssh2/commit/d34d9258b8420b19ec3f97b4cc5bf7aa7d98e35a 15 + ./CVE-2023-48795.patch 16 + ]; 17 + 12 18 outputs = [ "out" "dev" "devdoc" ]; 13 19 14 20 propagatedBuildInputs = [ openssl ]; # see Libs: in libssh2.pc