/* Provenance: curl/curl do_SOCKS5 (fixed). * repo: curl/curl * commit: fb4415d8aee6c1045be932a34fe6107c2f5ed147 * parent: 09e25b9d94f4106eac8ca3a43b221bdc66f405e4 * commit_url: https://github.com/curl/curl/commit/fb4415d8aee6c1045be932a34fe6107c2f5ed147 * cve: CVE-2023-38545 * license: curl * function: do_SOCKS5 * relpath: lib/socks.c * provenance: human * mechanism: unchecked_length_copy */ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, struct socks_state *sx, struct Curl_easy *data) { /* According to the RFC1928, section "6. Replies". This is what a SOCK5 replies: +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ Where: o VER protocol version: X'05' o REP Reply field: o X'00' succeeded */ struct connectdata *conn = cf->conn; unsigned char *socksreq = (unsigned char *)data->state.buffer; int idx; CURLcode result; CURLproxycode presult; bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; const size_t hostname_len = strlen(sx->hostname); ssize_t len = 0; const unsigned char auth = data->set.socks5auth; bool allow_gssapi = FALSE; struct Curl_dns_entry *dns = NULL; DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); switch(sx->state) { case CONNECT_SOCKS_INIT: if(conn->bits.httpproxy) infof(data, "SOCKS5: connecting to HTTP proxy %s port %d", sx->hostname, sx->remote_port); /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ if(!socks5_resolve_local && hostname_len > 255) { failf(data, "SOCKS5: the destination hostname is too long to be " "resolved remotely by the proxy."); return CURLPX_LONG_HOSTNAME; } if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) infof(data, "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %u", auth); if(!(auth & CURLAUTH_BASIC)) /* disable username/password auth */ sx->proxy_user = NULL; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(auth & CURLAUTH_GSSAPI) allow_gssapi = TRUE; #endif idx = 0; socksreq[idx++] = 5; /* version */ idx++; /* number of authentication methods */ socksreq[idx++] = 0; /* no authentication */ if(allow_gssapi) socksreq[idx++] = 1; /* GSS-API */ if(sx->proxy_user) socksreq[idx++] = 2; /* username/password */ /* write the number of authentication methods */ socksreq[1] = (unsigned char) (idx - 2); sx->outp = socksreq; sx->outstanding = idx; presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, "initial SOCKS5 request"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in sending state */ return CURLPX_OK; } sxstate(sx, data, CONNECT_SOCKS_READ); goto CONNECT_SOCKS_READ_INIT; case CONNECT_SOCKS_SEND: presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, "initial SOCKS5 request"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in sending state */ return CURLPX_OK; } /* FALLTHROUGH */ CONNECT_SOCKS_READ_INIT: case CONNECT_SOCKS_READ_INIT: sx->outstanding = 2; /* expect two bytes */ sx->outp = socksreq; /* store it here */ /* FALLTHROUGH */ case CONNECT_SOCKS_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, "initial SOCKS5 response"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in reading state */ return CURLPX_OK; } else if(socksreq[0] != 5) { failf(data, "Received invalid version in initial SOCKS5 response."); return CURLPX_BAD_VERSION; } else if(socksreq[1] == 0) { /* DONE! No authentication needed. Send request. */ sxstate(sx, data, CONNECT_REQ_INIT); goto CONNECT_REQ_INIT; } else if(socksreq[1] == 2) { /* regular name + password authentication */ sxstate(sx, data, CONNECT_AUTH_INIT); goto CONNECT_AUTH_INIT; } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else if(allow_gssapi && (socksreq[1] == 1)) { sxstate(sx, data, CONNECT_GSSAPI_INIT); result = Curl_SOCKS5_gssapi_negotiate(cf, data); if(result) { failf(data, "Unable to negotiate SOCKS5 GSS-API context."); return CURLPX_GSSAPI; } } #endif else { /* error */ if(!allow_gssapi && (socksreq[1] == 1)) { failf(data, "SOCKS5 GSSAPI per-message authentication is not supported."); return CURLPX_GSSAPI_PERMSG; } else if(socksreq[1] == 255) { failf(data, "No authentication method was acceptable."); return CURLPX_NO_AUTH; } } failf(data, "Undocumented SOCKS5 mode attempted to be used by server."); return CURLPX_UNKNOWN_MODE; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) case CONNECT_GSSAPI_INIT: /* GSSAPI stuff done non-blocking */ break; #endif default: /* do nothing! */ break; CONNECT_AUTH_INIT: case CONNECT_AUTH_INIT: { /* Needs user name and password */ size_t proxy_user_len, proxy_password_len; if(sx->proxy_user && sx->proxy_password) { proxy_user_len = strlen(sx->proxy_user); proxy_password_len = strlen(sx->proxy_password); } else { proxy_user_len = 0; proxy_password_len = 0; } /* username/password request looks like * +----+------+----------+------+----------+ * |VER | ULEN | UNAME | PLEN | PASSWD | * +----+------+----------+------+----------+ * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | * +----+------+----------+------+----------+ */ len = 0; socksreq[len++] = 1; /* username/pw subnegotiation version */ socksreq[len++] = (unsigned char) proxy_user_len; if(sx->proxy_user && proxy_user_len) { /* the length must fit in a single byte */ if(proxy_user_len > 255) { failf(data, "Excessive user name length for proxy auth"); return CURLPX_LONG_USER; } memcpy(socksreq + len, sx->proxy_user, proxy_user_len); } len += proxy_user_len; socksreq[len++] = (unsigned char) proxy_password_len; if(sx->proxy_password && proxy_password_len) { /* the length must fit in a single byte */ if(proxy_password_len > 255) { failf(data, "Excessive password length for proxy auth"); return CURLPX_LONG_PASSWD; } memcpy(socksreq + len, sx->proxy_password, proxy_password_len); } len += proxy_password_len; sxstate(sx, data, CONNECT_AUTH_SEND); sx->outstanding = len; sx->outp = socksreq; } /* FALLTHROUGH */ case CONNECT_AUTH_SEND: presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, "SOCKS5 sub-negotiation request"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in sending state */ return CURLPX_OK; } sx->outp = socksreq; sx->outstanding = 2; sxstate(sx, data, CONNECT_AUTH_READ); /* FALLTHROUGH */ case CONNECT_AUTH_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, "SOCKS5 sub-negotiation response"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in reading state */ return CURLPX_OK; } /* ignore the first (VER) byte */ else if(socksreq[1]) { /* status */ failf(data, "User was rejected by the SOCKS5 server (%d %d).", socksreq[0], socksreq[1]); return CURLPX_USER_REJECTED; } /* Everything is good so far, user was authenticated! */ sxstate(sx, data, CONNECT_REQ_INIT); /* FALLTHROUGH */ CONNECT_REQ_INIT: case CONNECT_REQ_INIT: if(socks5_resolve_local) { enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); if(rc == CURLRESOLV_ERROR) return CURLPX_RESOLVE_HOST; if(rc == CURLRESOLV_PENDING) { sxstate(sx, data, CONNECT_RESOLVING); return CURLPX_OK; } sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } goto CONNECT_RESOLVE_REMOTE; case CONNECT_RESOLVING: /* check if we have the name resolved by now */ dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); if(dns) { #ifdef CURLRES_ASYNCH data->state.async.dns = dns; data->state.async.done = TRUE; #endif infof(data, "SOCKS5: hostname '%s' found", sx->hostname); } if(!dns) { result = Curl_resolv_check(data, &dns); if(!dns) { if(result) return CURLPX_RESOLVE_HOST; return CURLPX_OK; } } /* FALLTHROUGH */ CONNECT_RESOLVED: case CONNECT_RESOLVED: { char dest[MAX_IPADR_LEN] = "unknown"; /* printable address */ struct Curl_addrinfo *hp = NULL; if(dns) hp = dns->addr; if(!hp) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname); return CURLPX_RESOLVE_HOST; } Curl_printable_address(hp, dest, sizeof(dest)); len = 0; socksreq[len++] = 5; /* version (SOCKS5) */ socksreq[len++] = 1; /* connect */ socksreq[len++] = 0; /* must be zero */ if(hp->ai_family == AF_INET) { int i; struct sockaddr_in *saddr_in; socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; for(i = 0; i < 4; i++) { socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i]; } infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest, sx->remote_port); } #ifdef ENABLE_IPV6 else if(hp->ai_family == AF_INET6) { int i; struct sockaddr_in6 *saddr_in6; socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr; for(i = 0; i < 16; i++) { socksreq[len++] = ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i]; } infof(data, "SOCKS5 connect to [%s]:%d (locally resolved)", dest, sx->remote_port); } #endif else { hp = NULL; /* fail! */ failf(data, "SOCKS5 connection to %s not supported", dest); } Curl_resolv_unlock(data, dns); /* not used anymore from now on */ goto CONNECT_REQ_SEND; } CONNECT_RESOLVE_REMOTE: case CONNECT_RESOLVE_REMOTE: /* Authentication is complete, now specify destination to the proxy */ len = 0; socksreq[len++] = 5; /* version (SOCKS5) */ socksreq[len++] = 1; /* connect */ socksreq[len++] = 0; /* must be zero */ if(!socks5_resolve_local) { /* ATYP: domain name = 3, IPv6 == 4, IPv4 == 1 */ unsigned char ip4[4]; #ifdef ENABLE_IPV6 if(conn->bits.ipv6_ip) { char ip6[16]; if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) return CURLPX_BAD_ADDRESS_TYPE; socksreq[len++] = 4; memcpy(&socksreq[len], ip6, sizeof(ip6)); len += sizeof(ip6); } else #endif if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) { socksreq[len++] = 1; memcpy(&socksreq[len], ip4, sizeof(ip4)); len += sizeof(ip4); } else { socksreq[len++] = 3; socksreq[len++] = (unsigned char) hostname_len; /* one byte length */ memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */ len += hostname_len; } infof(data, "SOCKS5 connect to %s:%d (remotely resolved)", sx->hostname, sx->remote_port); } /* FALLTHROUGH */ CONNECT_REQ_SEND: case CONNECT_REQ_SEND: /* PORT MSB */ socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* PORT LSB */ socksreq[len++] = (unsigned char)(sx->remote_port & 0xff); #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(conn->socks5_gssapi_enctype) { failf(data, "SOCKS5 GSS-API protection not yet implemented."); return CURLPX_GSSAPI_PROTECTION; } #endif sx->outp = socksreq; sx->outstanding = len; sxstate(sx, data, CONNECT_REQ_SENDING); /* FALLTHROUGH */ case CONNECT_REQ_SENDING: presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, "SOCKS5 connect request"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in send state */ return CURLPX_OK; } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(conn->socks5_gssapi_enctype) { failf(data, "SOCKS5 GSS-API protection not yet implemented."); return CURLPX_GSSAPI_PROTECTION; } #endif sx->outstanding = 10; /* minimum packet size is 10 */ sx->outp = socksreq; sxstate(sx, data, CONNECT_REQ_READ); /* FALLTHROUGH */ case CONNECT_REQ_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, "SOCKS5 connect request ack"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in reading state */ return CURLPX_OK; } else if(socksreq[0] != 5) { /* version */ failf(data, "SOCKS5 reply has wrong version, version should be 5."); return CURLPX_BAD_VERSION; } else if(socksreq[1]) { /* Anything besides 0 is an error */ CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; int code = socksreq[1]; failf(data, "Can't complete SOCKS5 connection to %s. (%d)", sx->hostname, (unsigned char)socksreq[1]); if(code < 9) { /* RFC 1928 section 6 lists: */ static const CURLproxycode lookup[] = { CURLPX_OK, CURLPX_REPLY_GENERAL_SERVER_FAILURE, CURLPX_REPLY_NOT_ALLOWED, CURLPX_REPLY_NETWORK_UNREACHABLE, CURLPX_REPLY_HOST_UNREACHABLE, CURLPX_REPLY_CONNECTION_REFUSED, CURLPX_REPLY_TTL_EXPIRED, CURLPX_REPLY_COMMAND_NOT_SUPPORTED, CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, }; rc = lookup[code]; } return rc; } /* Fix: in general, returned BND.ADDR is variable length parameter by RFC 1928, so the reply packet should be read until the end to avoid errors at subsequent protocol level. +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ ATYP: o IP v4 address: X'01', BND.ADDR = 4 byte o domain name: X'03', BND.ADDR = [ 1 byte length, string ] o IP v6 address: X'04', BND.ADDR = 16 byte */ /* Calculate real packet size */ if(socksreq[3] == 3) { /* domain name */ int addrlen = (int) socksreq[4]; len = 5 + addrlen + 2; } else if(socksreq[3] == 4) { /* IPv6 */ len = 4 + 16 + 2; } else if(socksreq[3] == 1) { len = 4 + 4 + 2; } else { failf(data, "SOCKS5 reply has wrong address type."); return CURLPX_BAD_ADDRESS_TYPE; } /* At this point we already read first 10 bytes */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(!conn->socks5_gssapi_enctype) { /* decrypt_gssapi_blockread already read the whole packet */ #endif if(len > 10) { sx->outstanding = len - 10; /* get the rest */ sx->outp = &socksreq[10]; sxstate(sx, data, CONNECT_REQ_READ_MORE); } else { sxstate(sx, data, CONNECT_DONE); break; } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } #endif /* FALLTHROUGH */ case CONNECT_REQ_READ_MORE: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, "SOCKS5 connect request address"); if(CURLPX_OK != presult) return presult; else if(sx->outstanding) { /* remain in reading state */ return CURLPX_OK; } sxstate(sx, data, CONNECT_DONE); } infof(data, "SOCKS5 request granted."); return CURLPX_OK; /* Proxy was successful! */ }