mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(real-world-vfc-slice): add labeled OpenSSL/Firefox/Chromium/curl pair corpus
Harvest 176 isolated vuln-vs-fix functions (109 CVEs, 352 training rows) into benchmarks/pairs/vfc with provenance and a JSONL training manifest. Score the slice as an honesty dashboard (ousast pairs --slice vfc); keep the pair gate on the local fixtures only.
This commit is contained in:
@@ -34,12 +34,19 @@ Pairs in this directory **must not** join `LANGUAGE_MANIFESTS`.
|
||||
- **sast** — OWASP Benchmark Java/Python true-vs-false files and Juliet
|
||||
bad-vs-good sinks (`benchmarks/pairs/sast/`). Score is Youden (TPR − FPR).
|
||||
Honesty dashboard, not a CI fail.
|
||||
- **vfc** — reviewed isolated functions from OpenSSL, Firefox, Chromium, and
|
||||
curl (`benchmarks/pairs/vfc/`). Overlay vs inventory scoreboard. Honesty
|
||||
dashboard, not a CI fail. Training labels (vuln vs fixed) are
|
||||
`vfc/training/manifest.jsonl` (vuln vs fixed, one snapshot per line).
|
||||
Grow with a blobless `git log --grep=CVE-202` mine plus `vfc/harvest.py`,
|
||||
then `vfc/generate_catalog.py` (maintainer, not CI).
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
uv run ousast pairs
|
||||
uv run ousast pairs --slice github --json
|
||||
uv run ousast pairs --slice vfc --json
|
||||
uv run python -m openultrasast.pair_gate
|
||||
```
|
||||
|
||||
|
||||
@@ -122,3 +122,32 @@ url = "https://github.com/tqrg/security-patches-dataset"
|
||||
level = "commit"
|
||||
languages = ["multi"]
|
||||
notes = "Ground-truth security patches mined from NVD, CVE Details, OSV."
|
||||
|
||||
[[dataset]]
|
||||
name = "OpenSSL"
|
||||
url = "https://github.com/openssl/openssl"
|
||||
level = "commit/function"
|
||||
languages = ["C"]
|
||||
notes = "Public git. Seed: CVE-2014-0160 tls1_process_heartbeat. Do not vendor the whole tree; harvest isolated functions."
|
||||
|
||||
[[dataset]]
|
||||
name = "Firefox mozilla-central"
|
||||
url = "https://hg.mozilla.org/mozilla-central"
|
||||
data = "https://github.com/mozilla-firefox/firefox"
|
||||
level = "commit/function"
|
||||
languages = ["C/C++", "JavaScript"]
|
||||
notes = "Canonical VCS is hg.mozilla.org; git mirrors are gecko-dev (archived) and mozilla-firefox/firefox. Filter CVEfixes/MoreFixes for gecko-dev. Do not clone mozilla-central. FixFox is embargoed until 2030. Seed: CVE-2020-15667 mar_insert_item."
|
||||
|
||||
[[dataset]]
|
||||
name = "Chromium"
|
||||
url = "https://github.com/chromium/chromium"
|
||||
level = "commit/function"
|
||||
languages = ["C/C++", "JavaScript"]
|
||||
notes = "Public git mirror of chromium/src. Seed: CVE-2019-5786 FileReaderLoader::ArrayBufferResult. Do not vendor the whole tree."
|
||||
|
||||
[[dataset]]
|
||||
name = "curl"
|
||||
url = "https://github.com/curl/curl"
|
||||
level = "commit/function"
|
||||
languages = ["C"]
|
||||
notes = "Public git. Seed: CVE-2023-38545 do_SOCKS5. Isolated functions only; do not vendor the tree."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
/* Provenance: Chromium FileReaderLoader::ArrayBufferResult after CVE-2019-5786.
|
||||
* repo: chromium/chromium
|
||||
* commit: ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449
|
||||
* parent: 9f3fccdba567f30ea39b08f57d8d5b49cd832b4a
|
||||
* commit_url: https://github.com/chromium/chromium/commit/ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449
|
||||
* cve: CVE-2019-5786
|
||||
* license: BSD-3-Clause
|
||||
* function: FileReaderLoader::ArrayBufferResult
|
||||
* relpath: third_party/blink/renderer/core/fileapi/file_reader_loader.cc
|
||||
*/
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
enum class FileErrorCode { kOK };
|
||||
|
||||
class ArrayBuffer {
|
||||
public:
|
||||
static void *Create(const void *data, size_t length) {
|
||||
(void)data;
|
||||
(void)length;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class DOMArrayBuffer {
|
||||
public:
|
||||
static DOMArrayBuffer *Create(void *buffer) {
|
||||
(void)buffer;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct RawData {
|
||||
void *ToArrayBuffer() { return nullptr; }
|
||||
size_t ByteLength() const { return 0; }
|
||||
const void *Data() const { return nullptr; }
|
||||
};
|
||||
|
||||
class FileReaderLoader {
|
||||
public:
|
||||
DOMArrayBuffer *ArrayBufferResult();
|
||||
|
||||
DOMArrayBuffer *array_buffer_result_ = nullptr;
|
||||
std::unique_ptr<RawData> raw_data_;
|
||||
FileErrorCode error_code_ = FileErrorCode::kOK;
|
||||
bool finished_loading_ = false;
|
||||
|
||||
void AdjustReportedMemoryUsageToV8(int64_t delta) { (void)delta; }
|
||||
};
|
||||
|
||||
DOMArrayBuffer *FileReaderLoader::ArrayBufferResult() {
|
||||
if (array_buffer_result_)
|
||||
return array_buffer_result_;
|
||||
|
||||
// If the loading is not started or an error occurs, return an empty result.
|
||||
if (!raw_data_ || error_code_ != FileErrorCode::kOK)
|
||||
return nullptr;
|
||||
|
||||
if (!finished_loading_) {
|
||||
return DOMArrayBuffer::Create(ArrayBuffer::Create(raw_data_->Data(), raw_data_->ByteLength()));
|
||||
}
|
||||
|
||||
array_buffer_result_ = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());
|
||||
AdjustReportedMemoryUsageToV8(-1 * static_cast<int64_t>(raw_data_->ByteLength()));
|
||||
raw_data_.reset();
|
||||
return array_buffer_result_;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Provenance: Chromium FileReaderLoader::ArrayBufferResult before CVE-2019-5786.
|
||||
* repo: chromium/chromium
|
||||
* commit: 9f3fccdba567f30ea39b08f57d8d5b49cd832b4a
|
||||
* parent: 9f3fccdba567f30ea39b08f57d8d5b49cd832b4a
|
||||
* fix: ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449
|
||||
* commit_url: https://github.com/chromium/chromium/commit/ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449
|
||||
* cve: CVE-2019-5786
|
||||
* license: BSD-3-Clause
|
||||
* function: FileReaderLoader::ArrayBufferResult
|
||||
* relpath: third_party/blink/renderer/core/fileapi/file_reader_loader.cc
|
||||
*/
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
enum class FileErrorCode { kOK };
|
||||
|
||||
class ArrayBuffer {
|
||||
public:
|
||||
static void *Create(const void *data, size_t length) {
|
||||
(void)data;
|
||||
(void)length;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class DOMArrayBuffer {
|
||||
public:
|
||||
static DOMArrayBuffer *Create(void *buffer) {
|
||||
(void)buffer;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct RawData {
|
||||
void *ToArrayBuffer() { return nullptr; }
|
||||
size_t ByteLength() const { return 0; }
|
||||
const void *Data() const { return nullptr; }
|
||||
};
|
||||
|
||||
class FileReaderLoader {
|
||||
public:
|
||||
DOMArrayBuffer *ArrayBufferResult();
|
||||
|
||||
DOMArrayBuffer *array_buffer_result_ = nullptr;
|
||||
std::unique_ptr<RawData> raw_data_;
|
||||
FileErrorCode error_code_ = FileErrorCode::kOK;
|
||||
bool finished_loading_ = false;
|
||||
|
||||
void AdjustReportedMemoryUsageToV8(int64_t delta) { (void)delta; }
|
||||
};
|
||||
|
||||
DOMArrayBuffer *FileReaderLoader::ArrayBufferResult() {
|
||||
if (array_buffer_result_)
|
||||
return array_buffer_result_;
|
||||
|
||||
// If the loading is not started or an error occurs, return an empty result.
|
||||
if (!raw_data_ || error_code_ != FileErrorCode::kOK)
|
||||
return nullptr;
|
||||
|
||||
DOMArrayBuffer *result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());
|
||||
if (finished_loading_) {
|
||||
array_buffer_result_ = result;
|
||||
AdjustReportedMemoryUsageToV8(-1 * static_cast<int64_t>(raw_data_->ByteLength()));
|
||||
raw_data_.reset();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Provenance: chromium/chromium IsShellIntegratedExtension (fixed).
|
||||
// repo: chromium/chromium
|
||||
// commit: 22c61cfae5d1b37c773e638d342488ae11118b51
|
||||
// parent: a07d0ae0a7dc68686adca10dcf11138cb2f4ab6d
|
||||
// commit_url: https://github.com/chromium/chromium/commit/22c61cfae5d1b37c773e638d342488ae11118b51
|
||||
// cve: CVE-2022-2162
|
||||
// license: BSD-3-Clause
|
||||
// function: IsShellIntegratedExtension
|
||||
// relpath: content/browser/file_system_access/file_system_chooser.cc
|
||||
|
||||
bool FileSystemChooser::IsShellIntegratedExtension(
|
||||
const base::FilePath::StringType& extension) {
|
||||
// TODO(https://crbug.com/1154757): Figure out some way to unify this with
|
||||
// net::IsSafePortablePathComponent, with the result probably ending up in
|
||||
// base/i18n/file_util_icu.h.
|
||||
// - For the sake of consistency across platforms, we sanitize '.lnk' and
|
||||
// '.local' files on all platforms (not just Windows)
|
||||
// - There are some extensions (i.e. '.scf') we would like to sanitize which
|
||||
// `net::GenerateFileName()` does not
|
||||
base::FilePath::StringType extension_lower =
|
||||
base::ToLowerASCII(GetLastExtension(extension));
|
||||
|
||||
// '.lnk' and '.scf' files may be used to execute arbitrary code (see
|
||||
// https://nvd.nist.gov/vuln/detail/CVE-2010-2568 and
|
||||
// https://crbug.com/1227995, respectively). '.local' files are used by
|
||||
// Windows to determine which DLLs to load for an application. '.url' files
|
||||
// can be used to read arbirtary files (see https://crbug.com/1307930).
|
||||
if ((extension_lower == FILE_PATH_LITERAL("lnk")) ||
|
||||
(extension_lower == FILE_PATH_LITERAL("local")) ||
|
||||
(extension_lower == FILE_PATH_LITERAL("scf")) ||
|
||||
(extension_lower == FILE_PATH_LITERAL("url"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setting a file's extension to a CLSID may conceal its actual file type on
|
||||
// some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420).
|
||||
if (!extension_lower.empty() &&
|
||||
(extension_lower.front() == FILE_PATH_LITERAL('{')) &&
|
||||
(extension_lower.back() == FILE_PATH_LITERAL('}'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Provenance: chromium/chromium IsShellIntegratedExtension (vuln).
|
||||
// repo: chromium/chromium
|
||||
// commit: a07d0ae0a7dc68686adca10dcf11138cb2f4ab6d
|
||||
// parent: a07d0ae0a7dc68686adca10dcf11138cb2f4ab6d
|
||||
// commit_url: https://github.com/chromium/chromium/commit/22c61cfae5d1b37c773e638d342488ae11118b51
|
||||
// cve: CVE-2022-2162
|
||||
// license: BSD-3-Clause
|
||||
// function: IsShellIntegratedExtension
|
||||
// relpath: content/browser/file_system_access/file_system_chooser.cc
|
||||
|
||||
bool FileSystemChooser::IsShellIntegratedExtension(
|
||||
const base::FilePath::StringType& extension) {
|
||||
// TODO(https://crbug.com/1154757): Figure out some way to unify this with
|
||||
// net::IsSafePortablePathComponent, with the result probably ending up in
|
||||
// base/i18n/file_util_icu.h.
|
||||
// - For the sake of consistency across platforms, we sanitize '.lnk' and
|
||||
// '.local' files on all platforms (not just Windows)
|
||||
// - There are some extensions (i.e. '.scf') we would like to sanitize which
|
||||
// `net::GenerateFileName()` does not
|
||||
base::FilePath::StringType extension_lower =
|
||||
base::ToLowerASCII(GetLastExtension(extension));
|
||||
|
||||
// .lnk and .scf files may be used to execute arbitrary code (see
|
||||
// https://nvd.nist.gov/vuln/detail/CVE-2010-2568 and
|
||||
// https://crbug.com/1227995, respectively). .local files are used by Windows
|
||||
// to determine which DLLs to load for an application.
|
||||
if ((extension_lower == FILE_PATH_LITERAL("lnk")) ||
|
||||
(extension_lower == FILE_PATH_LITERAL("local")) ||
|
||||
(extension_lower == FILE_PATH_LITERAL("scf"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setting a file's extension to a CLSID may conceal its actual file type on
|
||||
// some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420).
|
||||
if (!extension_lower.empty() &&
|
||||
(extension_lower.front() == FILE_PATH_LITERAL('{')) &&
|
||||
(extension_lower.back() == FILE_PATH_LITERAL('}'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/* Provenance: curl/curl tool_header_cb (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8236aba58542c5f89f1d41ca09d84579efb05e22
|
||||
* parent: 26d2755d7c3181e90e46014778941bff53d2309f
|
||||
* commit_url: https://github.com/curl/curl/commit/8236aba58542c5f89f1d41ca09d84579efb05e22
|
||||
* cve: CVE-2020-8177
|
||||
* license: curl
|
||||
* function: tool_header_cb
|
||||
* relpath: src/tool_cb_hdr.c
|
||||
*/
|
||||
|
||||
size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
struct per_transfer *per = userdata;
|
||||
struct HdrCbData *hdrcbdata = &per->hdrcbdata;
|
||||
struct OutStruct *outs = &per->outs;
|
||||
struct OutStruct *heads = &per->heads;
|
||||
struct OutStruct *etag_save = &per->etag_save;
|
||||
const char *str = ptr;
|
||||
const size_t cb = size * nmemb;
|
||||
const char *end = (char *)ptr + cb;
|
||||
long protocol = 0;
|
||||
|
||||
/*
|
||||
* Once that libcurl has called back tool_header_cb() the returned value
|
||||
* is checked against the amount that was intended to be written, if
|
||||
* it does not match then it fails with CURLE_WRITE_ERROR. So at this
|
||||
* point returning a value different from sz*nmemb indicates failure.
|
||||
*/
|
||||
size_t failure = (size && nmemb) ? 0 : 1;
|
||||
|
||||
if(!per->config)
|
||||
return failure;
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
if(size * nmemb > (size_t)CURL_MAX_HTTP_HEADER) {
|
||||
warnf(per->config->global, "Header data exceeds single call write "
|
||||
"limit!\n");
|
||||
return failure;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Write header data when curl option --dump-header (-D) is given.
|
||||
*/
|
||||
|
||||
if(per->config->headerfile && heads->stream) {
|
||||
size_t rc = fwrite(ptr, size, nmemb, heads->stream);
|
||||
if(rc != cb)
|
||||
return rc;
|
||||
/* flush the stream to send off what we got earlier */
|
||||
(void)fflush(heads->stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write etag to file when --etag-save option is given.
|
||||
* etag string that we want is enveloped in double quotes
|
||||
*/
|
||||
if(per->config->etag_save_file && etag_save->stream) {
|
||||
/* match only header that start with etag (case insensitive) */
|
||||
if(curl_strnequal(str, "etag:", 5)) {
|
||||
char *etag_h = NULL;
|
||||
char *first = NULL;
|
||||
char *last = NULL;
|
||||
size_t etag_length = 0;
|
||||
|
||||
etag_h = ptr;
|
||||
/* point to first occurrence of double quote */
|
||||
first = memchr(etag_h, '\"', cb);
|
||||
|
||||
/*
|
||||
* if server side messed with the etag header and doesn't include
|
||||
* double quotes around the etag, kindly exit with a warning
|
||||
*/
|
||||
|
||||
if(!first) {
|
||||
warnf(per->config->global,
|
||||
"\nReceived header etag is missing double quote/s\n");
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
/* discard first double quote */
|
||||
first++;
|
||||
}
|
||||
|
||||
/* point to last occurrence of double quote */
|
||||
last = memchr(first, '\"', cb);
|
||||
|
||||
if(!last) {
|
||||
warnf(per->config->global,
|
||||
"\nReceived header etag is missing double quote/s\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get length of desired etag */
|
||||
etag_length = (size_t)last - (size_t)first;
|
||||
|
||||
fwrite(first, size, etag_length, etag_save->stream);
|
||||
/* terminate with new line */
|
||||
fputc('\n', etag_save->stream);
|
||||
}
|
||||
|
||||
(void)fflush(etag_save->stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* This callback sets the filename where output shall be written when
|
||||
* curl options --remote-name (-O) and --remote-header-name (-J) have
|
||||
* been simultaneously given and additionally server returns an HTTP
|
||||
* Content-Disposition header specifying a filename property.
|
||||
*/
|
||||
|
||||
curl_easy_getinfo(per->curl, CURLINFO_PROTOCOL, &protocol);
|
||||
if(hdrcbdata->honor_cd_filename &&
|
||||
(cb > 20) && checkprefix("Content-disposition:", str) &&
|
||||
(protocol & (CURLPROTO_HTTPS|CURLPROTO_HTTP))) {
|
||||
const char *p = str + 20;
|
||||
|
||||
/* look for the 'filename=' parameter
|
||||
(encoded filenames (*=) are not supported) */
|
||||
for(;;) {
|
||||
char *filename;
|
||||
size_t len;
|
||||
|
||||
while(*p && (p < end) && !ISALPHA(*p))
|
||||
p++;
|
||||
if(p > end - 9)
|
||||
break;
|
||||
|
||||
if(memcmp(p, "filename=", 9)) {
|
||||
/* no match, find next parameter */
|
||||
while((p < end) && (*p != ';'))
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
p += 9;
|
||||
|
||||
/* this expression below typecasts 'cb' only to avoid
|
||||
warning: signed and unsigned type in conditional expression
|
||||
*/
|
||||
len = (ssize_t)cb - (p - str);
|
||||
filename = parse_filename(p, len);
|
||||
if(filename) {
|
||||
if(outs->stream) {
|
||||
/* indication of problem, get out! */
|
||||
free(filename);
|
||||
return failure;
|
||||
}
|
||||
|
||||
outs->is_cd_filename = TRUE;
|
||||
outs->s_isreg = TRUE;
|
||||
outs->fopened = FALSE;
|
||||
outs->filename = filename;
|
||||
outs->alloc_filename = TRUE;
|
||||
hdrcbdata->honor_cd_filename = FALSE; /* done now! */
|
||||
if(!tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(!outs->stream && !tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
}
|
||||
|
||||
if(hdrcbdata->config->show_headers &&
|
||||
(protocol &
|
||||
(CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_RTSP|CURLPROTO_FILE))) {
|
||||
/* bold headers only for selected protocols */
|
||||
char *value = NULL;
|
||||
|
||||
if(!outs->stream && !tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
|
||||
if(hdrcbdata->global->isatty && hdrcbdata->global->styled_output)
|
||||
value = memchr(ptr, ':', cb);
|
||||
if(value) {
|
||||
size_t namelen = value - ptr;
|
||||
fprintf(outs->stream, BOLD "%.*s" BOLDOFF ":", namelen, ptr);
|
||||
fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
|
||||
}
|
||||
else
|
||||
/* not "handled", just show it */
|
||||
fwrite(ptr, cb, 1, outs->stream);
|
||||
}
|
||||
return cb;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/* Provenance: curl/curl tool_header_cb (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 26d2755d7c3181e90e46014778941bff53d2309f
|
||||
* parent: 26d2755d7c3181e90e46014778941bff53d2309f
|
||||
* commit_url: https://github.com/curl/curl/commit/8236aba58542c5f89f1d41ca09d84579efb05e22
|
||||
* cve: CVE-2020-8177
|
||||
* license: curl
|
||||
* function: tool_header_cb
|
||||
* relpath: src/tool_cb_hdr.c
|
||||
*/
|
||||
|
||||
size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
struct per_transfer *per = userdata;
|
||||
struct HdrCbData *hdrcbdata = &per->hdrcbdata;
|
||||
struct OutStruct *outs = &per->outs;
|
||||
struct OutStruct *heads = &per->heads;
|
||||
struct OutStruct *etag_save = &per->etag_save;
|
||||
const char *str = ptr;
|
||||
const size_t cb = size * nmemb;
|
||||
const char *end = (char *)ptr + cb;
|
||||
long protocol = 0;
|
||||
|
||||
/*
|
||||
* Once that libcurl has called back tool_header_cb() the returned value
|
||||
* is checked against the amount that was intended to be written, if
|
||||
* it does not match then it fails with CURLE_WRITE_ERROR. So at this
|
||||
* point returning a value different from sz*nmemb indicates failure.
|
||||
*/
|
||||
size_t failure = (size && nmemb) ? 0 : 1;
|
||||
|
||||
if(!per->config)
|
||||
return failure;
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
if(size * nmemb > (size_t)CURL_MAX_HTTP_HEADER) {
|
||||
warnf(per->config->global, "Header data exceeds single call write "
|
||||
"limit!\n");
|
||||
return failure;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Write header data when curl option --dump-header (-D) is given.
|
||||
*/
|
||||
|
||||
if(per->config->headerfile && heads->stream) {
|
||||
size_t rc = fwrite(ptr, size, nmemb, heads->stream);
|
||||
if(rc != cb)
|
||||
return rc;
|
||||
/* flush the stream to send off what we got earlier */
|
||||
(void)fflush(heads->stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write etag to file when --etag-save option is given.
|
||||
* etag string that we want is enveloped in double quotes
|
||||
*/
|
||||
if(per->config->etag_save_file && etag_save->stream) {
|
||||
/* match only header that start with etag (case insensitive) */
|
||||
if(curl_strnequal(str, "etag:", 5)) {
|
||||
char *etag_h = NULL;
|
||||
char *first = NULL;
|
||||
char *last = NULL;
|
||||
size_t etag_length = 0;
|
||||
|
||||
etag_h = ptr;
|
||||
/* point to first occurrence of double quote */
|
||||
first = memchr(etag_h, '\"', cb);
|
||||
|
||||
/*
|
||||
* if server side messed with the etag header and doesn't include
|
||||
* double quotes around the etag, kindly exit with a warning
|
||||
*/
|
||||
|
||||
if(!first) {
|
||||
warnf(per->config->global,
|
||||
"\nReceived header etag is missing double quote/s\n");
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
/* discard first double quote */
|
||||
first++;
|
||||
}
|
||||
|
||||
/* point to last occurrence of double quote */
|
||||
last = memchr(first, '\"', cb);
|
||||
|
||||
if(!last) {
|
||||
warnf(per->config->global,
|
||||
"\nReceived header etag is missing double quote/s\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get length of desired etag */
|
||||
etag_length = (size_t)last - (size_t)first;
|
||||
|
||||
fwrite(first, size, etag_length, etag_save->stream);
|
||||
/* terminate with new line */
|
||||
fputc('\n', etag_save->stream);
|
||||
}
|
||||
|
||||
(void)fflush(etag_save->stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* This callback sets the filename where output shall be written when
|
||||
* curl options --remote-name (-O) and --remote-header-name (-J) have
|
||||
* been simultaneously given and additionally server returns an HTTP
|
||||
* Content-Disposition header specifying a filename property.
|
||||
*/
|
||||
|
||||
curl_easy_getinfo(per->curl, CURLINFO_PROTOCOL, &protocol);
|
||||
if(hdrcbdata->honor_cd_filename &&
|
||||
(cb > 20) && checkprefix("Content-disposition:", str) &&
|
||||
(protocol & (CURLPROTO_HTTPS|CURLPROTO_HTTP))) {
|
||||
const char *p = str + 20;
|
||||
|
||||
/* look for the 'filename=' parameter
|
||||
(encoded filenames (*=) are not supported) */
|
||||
for(;;) {
|
||||
char *filename;
|
||||
size_t len;
|
||||
|
||||
while(*p && (p < end) && !ISALPHA(*p))
|
||||
p++;
|
||||
if(p > end - 9)
|
||||
break;
|
||||
|
||||
if(memcmp(p, "filename=", 9)) {
|
||||
/* no match, find next parameter */
|
||||
while((p < end) && (*p != ';'))
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
p += 9;
|
||||
|
||||
/* this expression below typecasts 'cb' only to avoid
|
||||
warning: signed and unsigned type in conditional expression
|
||||
*/
|
||||
len = (ssize_t)cb - (p - str);
|
||||
filename = parse_filename(p, len);
|
||||
if(filename) {
|
||||
if(outs->stream) {
|
||||
int rc;
|
||||
/* already opened and possibly written to */
|
||||
if(outs->fopened)
|
||||
fclose(outs->stream);
|
||||
outs->stream = NULL;
|
||||
|
||||
/* rename the initial file name to the new file name */
|
||||
rc = rename(outs->filename, filename);
|
||||
if(rc != 0) {
|
||||
warnf(per->config->global, "Failed to rename %s -> %s: %s\n",
|
||||
outs->filename, filename, strerror(errno));
|
||||
}
|
||||
if(outs->alloc_filename)
|
||||
Curl_safefree(outs->filename);
|
||||
if(rc != 0) {
|
||||
free(filename);
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
outs->is_cd_filename = TRUE;
|
||||
outs->s_isreg = TRUE;
|
||||
outs->fopened = FALSE;
|
||||
outs->filename = filename;
|
||||
outs->alloc_filename = TRUE;
|
||||
hdrcbdata->honor_cd_filename = FALSE; /* done now! */
|
||||
if(!tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(!outs->stream && !tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
}
|
||||
|
||||
if(hdrcbdata->config->show_headers &&
|
||||
(protocol &
|
||||
(CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_RTSP|CURLPROTO_FILE))) {
|
||||
/* bold headers only for selected protocols */
|
||||
char *value = NULL;
|
||||
|
||||
if(!outs->stream && !tool_create_output_file(outs, per->config))
|
||||
return failure;
|
||||
|
||||
if(hdrcbdata->global->isatty && hdrcbdata->global->styled_output)
|
||||
value = memchr(ptr, ':', cb);
|
||||
if(value) {
|
||||
size_t namelen = value - ptr;
|
||||
fprintf(outs->stream, BOLD "%.*s" BOLDOFF ":", namelen, ptr);
|
||||
fwrite(&value[1], cb - namelen - 1, 1, outs->stream);
|
||||
}
|
||||
else
|
||||
/* not "handled", just show it */
|
||||
fwrite(ptr, cb, 1, outs->stream);
|
||||
}
|
||||
return cb;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/* Provenance: curl/curl curl_multi_add_handle (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* parent: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* commit_url: https://github.com/curl/curl/commit/3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* cve: CVE-2020-8231
|
||||
* license: curl
|
||||
* function: curl_multi_add_handle
|
||||
* relpath: lib/multi.c
|
||||
*/
|
||||
|
||||
CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
/* First, make some basic checks that the CURLM handle is a good handle */
|
||||
if(!GOOD_MULTI_HANDLE(multi))
|
||||
return CURLM_BAD_HANDLE;
|
||||
|
||||
/* Verify that we got a somewhat good easy handle too */
|
||||
if(!GOOD_EASY_HANDLE(data))
|
||||
return CURLM_BAD_EASY_HANDLE;
|
||||
|
||||
/* Prevent users from adding same easy handle more than once and prevent
|
||||
adding to more than one multi stack */
|
||||
if(data->multi)
|
||||
return CURLM_ADDED_ALREADY;
|
||||
|
||||
if(multi->in_callback)
|
||||
return CURLM_RECURSIVE_API_CALL;
|
||||
|
||||
/* Initialize timeout list for this handle */
|
||||
Curl_llist_init(&data->state.timeoutlist, NULL);
|
||||
|
||||
/*
|
||||
* No failure allowed in this function beyond this point. And no
|
||||
* modification of easy nor multi handle allowed before this except for
|
||||
* potential multi's connection cache growing which won't be undone in this
|
||||
* function no matter what.
|
||||
*/
|
||||
if(data->set.errorbuffer)
|
||||
data->set.errorbuffer[0] = 0;
|
||||
|
||||
/* set the easy handle */
|
||||
multistate(data, CURLM_STATE_INIT);
|
||||
|
||||
/* for multi interface connections, we share DNS cache automatically if the
|
||||
easy handle's one is currently not set. */
|
||||
if(!data->dns.hostcache ||
|
||||
(data->dns.hostcachetype == HCACHE_NONE)) {
|
||||
data->dns.hostcache = &multi->hostcache;
|
||||
data->dns.hostcachetype = HCACHE_MULTI;
|
||||
}
|
||||
|
||||
/* Point to the shared or multi handle connection cache */
|
||||
if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT)))
|
||||
data->state.conn_cache = &data->share->conn_cache;
|
||||
else
|
||||
data->state.conn_cache = &multi->conn_cache;
|
||||
data->state.lastconnect_id = -1;
|
||||
|
||||
#ifdef USE_LIBPSL
|
||||
/* Do the same for PSL. */
|
||||
if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL)))
|
||||
data->psl = &data->share->psl;
|
||||
else
|
||||
data->psl = &multi->psl;
|
||||
#endif
|
||||
|
||||
/* We add the new entry last in the list. */
|
||||
data->next = NULL; /* end of the line */
|
||||
if(multi->easyp) {
|
||||
struct Curl_easy *last = multi->easylp;
|
||||
last->next = data;
|
||||
data->prev = last;
|
||||
multi->easylp = data; /* the new last node */
|
||||
}
|
||||
else {
|
||||
/* first node, make prev NULL! */
|
||||
data->prev = NULL;
|
||||
multi->easylp = multi->easyp = data; /* both first and last */
|
||||
}
|
||||
|
||||
/* make the Curl_easy refer back to this multi handle */
|
||||
data->multi = multi;
|
||||
|
||||
/* Set the timeout for this handle to expire really soon so that it will
|
||||
be taken care of even when this handle is added in the midst of operation
|
||||
when only the curl_multi_socket() API is used. During that flow, only
|
||||
sockets that time-out or have actions will be dealt with. Since this
|
||||
handle has no action yet, we make sure it times out to get things to
|
||||
happen. */
|
||||
Curl_expire(data, 0, EXPIRE_RUN_NOW);
|
||||
|
||||
/* increase the node-counter */
|
||||
multi->num_easy++;
|
||||
|
||||
/* increase the alive-counter */
|
||||
multi->num_alive++;
|
||||
|
||||
/* A somewhat crude work-around for a little glitch in Curl_update_timer()
|
||||
that happens if the lastcall time is set to the same time when the handle
|
||||
is removed as when the next handle is added, as then the check in
|
||||
Curl_update_timer() that prevents calling the application multiple times
|
||||
with the same timer info will not trigger and then the new handle's
|
||||
timeout will not be notified to the app.
|
||||
|
||||
The work-around is thus simply to clear the 'lastcall' variable to force
|
||||
Curl_update_timer() to always trigger a callback to the app when a new
|
||||
easy handle is added */
|
||||
memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall));
|
||||
|
||||
CONNCACHE_LOCK(data);
|
||||
/* The closure handle only ever has default timeouts set. To improve the
|
||||
state somewhat we clone the timeouts from each added handle so that the
|
||||
closure handle always has the same timeouts as the most recently added
|
||||
easy handle. */
|
||||
data->state.conn_cache->closure_handle->set.timeout = data->set.timeout;
|
||||
data->state.conn_cache->closure_handle->set.server_response_timeout =
|
||||
data->set.server_response_timeout;
|
||||
data->state.conn_cache->closure_handle->set.no_signal =
|
||||
data->set.no_signal;
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
Curl_update_timer(multi);
|
||||
return CURLM_OK;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/* Provenance: curl/curl curl_multi_add_handle (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* parent: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* commit_url: https://github.com/curl/curl/commit/3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* cve: CVE-2020-8231
|
||||
* license: curl
|
||||
* function: curl_multi_add_handle
|
||||
* relpath: lib/multi.c
|
||||
*/
|
||||
|
||||
CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
/* First, make some basic checks that the CURLM handle is a good handle */
|
||||
if(!GOOD_MULTI_HANDLE(multi))
|
||||
return CURLM_BAD_HANDLE;
|
||||
|
||||
/* Verify that we got a somewhat good easy handle too */
|
||||
if(!GOOD_EASY_HANDLE(data))
|
||||
return CURLM_BAD_EASY_HANDLE;
|
||||
|
||||
/* Prevent users from adding same easy handle more than once and prevent
|
||||
adding to more than one multi stack */
|
||||
if(data->multi)
|
||||
return CURLM_ADDED_ALREADY;
|
||||
|
||||
if(multi->in_callback)
|
||||
return CURLM_RECURSIVE_API_CALL;
|
||||
|
||||
/* Initialize timeout list for this handle */
|
||||
Curl_llist_init(&data->state.timeoutlist, NULL);
|
||||
|
||||
/*
|
||||
* No failure allowed in this function beyond this point. And no
|
||||
* modification of easy nor multi handle allowed before this except for
|
||||
* potential multi's connection cache growing which won't be undone in this
|
||||
* function no matter what.
|
||||
*/
|
||||
if(data->set.errorbuffer)
|
||||
data->set.errorbuffer[0] = 0;
|
||||
|
||||
/* set the easy handle */
|
||||
multistate(data, CURLM_STATE_INIT);
|
||||
|
||||
/* for multi interface connections, we share DNS cache automatically if the
|
||||
easy handle's one is currently not set. */
|
||||
if(!data->dns.hostcache ||
|
||||
(data->dns.hostcachetype == HCACHE_NONE)) {
|
||||
data->dns.hostcache = &multi->hostcache;
|
||||
data->dns.hostcachetype = HCACHE_MULTI;
|
||||
}
|
||||
|
||||
/* Point to the shared or multi handle connection cache */
|
||||
if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT)))
|
||||
data->state.conn_cache = &data->share->conn_cache;
|
||||
else
|
||||
data->state.conn_cache = &multi->conn_cache;
|
||||
|
||||
#ifdef USE_LIBPSL
|
||||
/* Do the same for PSL. */
|
||||
if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL)))
|
||||
data->psl = &data->share->psl;
|
||||
else
|
||||
data->psl = &multi->psl;
|
||||
#endif
|
||||
|
||||
/* We add the new entry last in the list. */
|
||||
data->next = NULL; /* end of the line */
|
||||
if(multi->easyp) {
|
||||
struct Curl_easy *last = multi->easylp;
|
||||
last->next = data;
|
||||
data->prev = last;
|
||||
multi->easylp = data; /* the new last node */
|
||||
}
|
||||
else {
|
||||
/* first node, make prev NULL! */
|
||||
data->prev = NULL;
|
||||
multi->easylp = multi->easyp = data; /* both first and last */
|
||||
}
|
||||
|
||||
/* make the Curl_easy refer back to this multi handle */
|
||||
data->multi = multi;
|
||||
|
||||
/* Set the timeout for this handle to expire really soon so that it will
|
||||
be taken care of even when this handle is added in the midst of operation
|
||||
when only the curl_multi_socket() API is used. During that flow, only
|
||||
sockets that time-out or have actions will be dealt with. Since this
|
||||
handle has no action yet, we make sure it times out to get things to
|
||||
happen. */
|
||||
Curl_expire(data, 0, EXPIRE_RUN_NOW);
|
||||
|
||||
/* increase the node-counter */
|
||||
multi->num_easy++;
|
||||
|
||||
/* increase the alive-counter */
|
||||
multi->num_alive++;
|
||||
|
||||
/* A somewhat crude work-around for a little glitch in Curl_update_timer()
|
||||
that happens if the lastcall time is set to the same time when the handle
|
||||
is removed as when the next handle is added, as then the check in
|
||||
Curl_update_timer() that prevents calling the application multiple times
|
||||
with the same timer info will not trigger and then the new handle's
|
||||
timeout will not be notified to the app.
|
||||
|
||||
The work-around is thus simply to clear the 'lastcall' variable to force
|
||||
Curl_update_timer() to always trigger a callback to the app when a new
|
||||
easy handle is added */
|
||||
memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall));
|
||||
|
||||
CONNCACHE_LOCK(data);
|
||||
/* The closure handle only ever has default timeouts set. To improve the
|
||||
state somewhat we clone the timeouts from each added handle so that the
|
||||
closure handle always has the same timeouts as the most recently added
|
||||
easy handle. */
|
||||
data->state.conn_cache->closure_handle->set.timeout = data->set.timeout;
|
||||
data->state.conn_cache->closure_handle->set.server_response_timeout =
|
||||
data->set.server_response_timeout;
|
||||
data->state.conn_cache->closure_handle->set.no_signal =
|
||||
data->set.no_signal;
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
Curl_update_timer(multi);
|
||||
return CURLM_OK;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Provenance: curl/curl Curl_open (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* parent: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* commit_url: https://github.com/curl/curl/commit/3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* cve: CVE-2020-8231
|
||||
* license: curl
|
||||
* function: Curl_open
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
* Curl_open()
|
||||
*
|
||||
* @param curl is a pointer to a sessionhandle pointer that gets set by this
|
||||
* function.
|
||||
* @return CURLcode
|
||||
*/
|
||||
|
||||
CURLcode Curl_open(struct Curl_easy **curl)
|
||||
{
|
||||
CURLcode result;
|
||||
struct Curl_easy *data;
|
||||
|
||||
/* Very simple start-up: alloc the struct, init it with zeroes and return */
|
||||
data = calloc(1, sizeof(struct Curl_easy));
|
||||
if(!data) {
|
||||
/* this is a very serious error */
|
||||
DEBUGF(fprintf(stderr, "Error: calloc of Curl_easy failed\n"));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
data->magic = CURLEASY_MAGIC_NUMBER;
|
||||
|
||||
result = Curl_resolver_init(data, &data->state.resolver);
|
||||
if(result) {
|
||||
DEBUGF(fprintf(stderr, "Error: resolver_init failed\n"));
|
||||
free(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = Curl_init_userdefined(data);
|
||||
if(!result) {
|
||||
Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER);
|
||||
Curl_convert_init(data);
|
||||
Curl_initinfo(data);
|
||||
|
||||
/* most recent connection is not yet defined */
|
||||
data->state.lastconnect_id = -1;
|
||||
|
||||
data->progress.flags |= PGRS_HIDE;
|
||||
data->state.current_speed = -1; /* init to negative == impossible */
|
||||
}
|
||||
|
||||
if(result) {
|
||||
Curl_resolver_cleanup(data->state.resolver);
|
||||
Curl_dyn_free(&data->state.headerb);
|
||||
Curl_freeset(data);
|
||||
free(data);
|
||||
data = NULL;
|
||||
}
|
||||
else
|
||||
*curl = data;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Provenance: curl/curl Curl_open (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* parent: 687908c6e6332b2bf4ba74b271e795f9c65a5a61
|
||||
* commit_url: https://github.com/curl/curl/commit/3c9e021f86872baae412a427e807fbfa2f3e8a22
|
||||
* cve: CVE-2020-8231
|
||||
* license: curl
|
||||
* function: Curl_open
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
* Curl_open()
|
||||
*
|
||||
* @param curl is a pointer to a sessionhandle pointer that gets set by this
|
||||
* function.
|
||||
* @return CURLcode
|
||||
*/
|
||||
|
||||
CURLcode Curl_open(struct Curl_easy **curl)
|
||||
{
|
||||
CURLcode result;
|
||||
struct Curl_easy *data;
|
||||
|
||||
/* Very simple start-up: alloc the struct, init it with zeroes and return */
|
||||
data = calloc(1, sizeof(struct Curl_easy));
|
||||
if(!data) {
|
||||
/* this is a very serious error */
|
||||
DEBUGF(fprintf(stderr, "Error: calloc of Curl_easy failed\n"));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
data->magic = CURLEASY_MAGIC_NUMBER;
|
||||
|
||||
result = Curl_resolver_init(data, &data->state.resolver);
|
||||
if(result) {
|
||||
DEBUGF(fprintf(stderr, "Error: resolver_init failed\n"));
|
||||
free(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = Curl_init_userdefined(data);
|
||||
if(!result) {
|
||||
Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER);
|
||||
Curl_convert_init(data);
|
||||
Curl_initinfo(data);
|
||||
|
||||
/* most recent connection is not yet defined */
|
||||
data->state.lastconnect = NULL;
|
||||
|
||||
data->progress.flags |= PGRS_HIDE;
|
||||
data->state.current_speed = -1; /* init to negative == impossible */
|
||||
}
|
||||
|
||||
if(result) {
|
||||
Curl_resolver_cleanup(data->state.resolver);
|
||||
Curl_dyn_free(&data->state.headerb);
|
||||
Curl_freeset(data);
|
||||
free(data);
|
||||
data = NULL;
|
||||
}
|
||||
else
|
||||
*curl = data;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Provenance: curl/curl config_init (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* parent: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* commit_url: https://github.com/curl/curl/commit/ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* cve: CVE-2020-8284
|
||||
* license: curl
|
||||
* function: config_init
|
||||
* relpath: src/tool_cfgable.c
|
||||
*/
|
||||
|
||||
void config_init(struct OperationConfig *config)
|
||||
{
|
||||
memset(config, 0, sizeof(struct OperationConfig));
|
||||
|
||||
config->postfieldsize = -1;
|
||||
config->use_httpget = FALSE;
|
||||
config->create_dirs = FALSE;
|
||||
config->maxredirs = DEFAULT_MAXREDIRS;
|
||||
config->proto = CURLPROTO_ALL;
|
||||
config->proto_present = FALSE;
|
||||
config->proto_redir = CURLPROTO_ALL & /* All except FILE, SCP and SMB */
|
||||
~(CURLPROTO_FILE | CURLPROTO_SCP | CURLPROTO_SMB |
|
||||
CURLPROTO_SMBS);
|
||||
config->proto_redir_present = FALSE;
|
||||
config->proto_default = NULL;
|
||||
config->tcp_nodelay = TRUE; /* enabled by default */
|
||||
config->happy_eyeballs_timeout_ms = CURL_HET_DEFAULT;
|
||||
config->http09_allowed = FALSE;
|
||||
config->ftp_skip_ip = TRUE;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Provenance: curl/curl config_init (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* parent: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* commit_url: https://github.com/curl/curl/commit/ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* cve: CVE-2020-8284
|
||||
* license: curl
|
||||
* function: config_init
|
||||
* relpath: src/tool_cfgable.c
|
||||
*/
|
||||
|
||||
void config_init(struct OperationConfig *config)
|
||||
{
|
||||
memset(config, 0, sizeof(struct OperationConfig));
|
||||
|
||||
config->postfieldsize = -1;
|
||||
config->use_httpget = FALSE;
|
||||
config->create_dirs = FALSE;
|
||||
config->maxredirs = DEFAULT_MAXREDIRS;
|
||||
config->proto = CURLPROTO_ALL;
|
||||
config->proto_present = FALSE;
|
||||
config->proto_redir = CURLPROTO_ALL & /* All except FILE, SCP and SMB */
|
||||
~(CURLPROTO_FILE | CURLPROTO_SCP | CURLPROTO_SMB |
|
||||
CURLPROTO_SMBS);
|
||||
config->proto_redir_present = FALSE;
|
||||
config->proto_default = NULL;
|
||||
config->tcp_nodelay = TRUE; /* enabled by default */
|
||||
config->happy_eyeballs_timeout_ms = CURL_HET_DEFAULT;
|
||||
config->http09_allowed = FALSE;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Provenance: curl/curl Curl_init_userdefined (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* parent: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* commit_url: https://github.com/curl/curl/commit/ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* cve: CVE-2020-8284
|
||||
* license: curl
|
||||
* function: Curl_init_userdefined
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_init_userdefined(struct Curl_easy *data)
|
||||
{
|
||||
struct UserDefined *set = &data->set;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
set->out = stdout; /* default output to stdout */
|
||||
set->in_set = stdin; /* default input from stdin */
|
||||
set->err = stderr; /* default stderr to stderr */
|
||||
|
||||
/* use fwrite as default function to store output */
|
||||
set->fwrite_func = (curl_write_callback)fwrite;
|
||||
|
||||
/* use fread as default function to read input */
|
||||
set->fread_func_set = (curl_read_callback)fread;
|
||||
set->is_fread_set = 0;
|
||||
set->is_fwrite_set = 0;
|
||||
|
||||
set->seek_func = ZERO_NULL;
|
||||
set->seek_client = ZERO_NULL;
|
||||
|
||||
/* conversion callbacks for non-ASCII hosts */
|
||||
set->convfromnetwork = ZERO_NULL;
|
||||
set->convtonetwork = ZERO_NULL;
|
||||
set->convfromutf8 = ZERO_NULL;
|
||||
|
||||
set->filesize = -1; /* we don't know the size */
|
||||
set->postfieldsize = -1; /* unknown size */
|
||||
set->maxredirs = -1; /* allow any amount by default */
|
||||
|
||||
set->method = HTTPREQ_GET; /* Default HTTP request */
|
||||
set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */
|
||||
set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */
|
||||
set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */
|
||||
set->ftp_filemethod = FTPFILE_MULTICWD;
|
||||
set->ftp_skip_ip = TRUE; /* skip PASV IP by default */
|
||||
#endif
|
||||
set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */
|
||||
|
||||
/* Set the default size of the SSL session ID cache */
|
||||
set->general_ssl.max_ssl_sessions = 5;
|
||||
|
||||
set->proxyport = 0;
|
||||
set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
|
||||
set->httpauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
|
||||
/* SOCKS5 proxy auth defaults to username/password + GSS-API */
|
||||
set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
|
||||
|
||||
/* make libcurl quiet by default: */
|
||||
set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */
|
||||
|
||||
Curl_mime_initpart(&set->mimepost, data);
|
||||
|
||||
/*
|
||||
* libcurl 7.10 introduced SSL verification *by default*! This needs to be
|
||||
* switched off unless wanted.
|
||||
*/
|
||||
set->ssl.primary.verifypeer = TRUE;
|
||||
set->ssl.primary.verifyhost = TRUE;
|
||||
#ifdef USE_TLS_SRP
|
||||
set->ssl.authtype = CURL_TLSAUTH_NONE;
|
||||
#endif
|
||||
set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth
|
||||
type */
|
||||
set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
|
||||
default */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
set->proxy_ssl = set->ssl;
|
||||
#endif
|
||||
|
||||
set->new_file_perms = 0644; /* Default permissions */
|
||||
set->new_directory_perms = 0755; /* Default permissions */
|
||||
|
||||
/* for the *protocols fields we don't use the CURLPROTO_ALL convenience
|
||||
define since we internally only use the lower 16 bits for the passed
|
||||
in bitmask to not conflict with the private bits */
|
||||
set->allowed_protocols = CURLPROTO_ALL;
|
||||
set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
|
||||
CURLPROTO_FTPS;
|
||||
|
||||
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
|
||||
/*
|
||||
* disallow unprotected protection negotiation NEC reference implementation
|
||||
* seem not to follow rfc1961 section 4.3/4.4
|
||||
*/
|
||||
set->socks5_gssapi_nec = FALSE;
|
||||
#endif
|
||||
|
||||
/* Set the default CA cert bundle/path detected/specified at build time.
|
||||
*
|
||||
* If Schannel is the selected SSL backend then these locations are
|
||||
* ignored. We allow setting CA location for schannel only when explicitly
|
||||
* specified by the user via CURLOPT_CAINFO / --cacert.
|
||||
*/
|
||||
if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) {
|
||||
#if defined(CURL_CA_BUNDLE)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_ORIG], CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY],
|
||||
CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
#if defined(CURL_CA_PATH)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_ORIG], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
set->wildcard_enabled = FALSE;
|
||||
set->chunk_bgn = ZERO_NULL;
|
||||
set->chunk_end = ZERO_NULL;
|
||||
set->tcp_keepalive = FALSE;
|
||||
set->tcp_keepintvl = 60;
|
||||
set->tcp_keepidle = 60;
|
||||
set->tcp_fastopen = FALSE;
|
||||
set->tcp_nodelay = TRUE;
|
||||
set->ssl_enable_npn = TRUE;
|
||||
set->ssl_enable_alpn = TRUE;
|
||||
set->expect_100_timeout = 1000L; /* Wait for a second by default. */
|
||||
set->sep_headers = TRUE; /* separated header lists by default */
|
||||
set->buffer_size = READBUFFER_SIZE;
|
||||
set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
|
||||
set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
|
||||
set->fnmatch = ZERO_NULL;
|
||||
set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
|
||||
set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
|
||||
set->maxage_conn = 118;
|
||||
set->http09_allowed = FALSE;
|
||||
set->httpversion =
|
||||
#ifdef USE_NGHTTP2
|
||||
CURL_HTTP_VERSION_2TLS
|
||||
#else
|
||||
CURL_HTTP_VERSION_1_1
|
||||
#endif
|
||||
;
|
||||
Curl_http2_init_userset(set);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/* Provenance: curl/curl Curl_init_userdefined (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* parent: abd846c374c5269809ce11740754bc6f00a0b193
|
||||
* commit_url: https://github.com/curl/curl/commit/ec9cc725d598ac77de7b6df8afeec292b3c8ad46
|
||||
* cve: CVE-2020-8284
|
||||
* license: curl
|
||||
* function: Curl_init_userdefined
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_init_userdefined(struct Curl_easy *data)
|
||||
{
|
||||
struct UserDefined *set = &data->set;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
set->out = stdout; /* default output to stdout */
|
||||
set->in_set = stdin; /* default input from stdin */
|
||||
set->err = stderr; /* default stderr to stderr */
|
||||
|
||||
/* use fwrite as default function to store output */
|
||||
set->fwrite_func = (curl_write_callback)fwrite;
|
||||
|
||||
/* use fread as default function to read input */
|
||||
set->fread_func_set = (curl_read_callback)fread;
|
||||
set->is_fread_set = 0;
|
||||
set->is_fwrite_set = 0;
|
||||
|
||||
set->seek_func = ZERO_NULL;
|
||||
set->seek_client = ZERO_NULL;
|
||||
|
||||
/* conversion callbacks for non-ASCII hosts */
|
||||
set->convfromnetwork = ZERO_NULL;
|
||||
set->convtonetwork = ZERO_NULL;
|
||||
set->convfromutf8 = ZERO_NULL;
|
||||
|
||||
set->filesize = -1; /* we don't know the size */
|
||||
set->postfieldsize = -1; /* unknown size */
|
||||
set->maxredirs = -1; /* allow any amount by default */
|
||||
|
||||
set->method = HTTPREQ_GET; /* Default HTTP request */
|
||||
set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */
|
||||
set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */
|
||||
set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */
|
||||
set->ftp_filemethod = FTPFILE_MULTICWD;
|
||||
#endif
|
||||
set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */
|
||||
|
||||
/* Set the default size of the SSL session ID cache */
|
||||
set->general_ssl.max_ssl_sessions = 5;
|
||||
|
||||
set->proxyport = 0;
|
||||
set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
|
||||
set->httpauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
|
||||
/* SOCKS5 proxy auth defaults to username/password + GSS-API */
|
||||
set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
|
||||
|
||||
/* make libcurl quiet by default: */
|
||||
set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */
|
||||
|
||||
Curl_mime_initpart(&set->mimepost, data);
|
||||
|
||||
/*
|
||||
* libcurl 7.10 introduced SSL verification *by default*! This needs to be
|
||||
* switched off unless wanted.
|
||||
*/
|
||||
set->ssl.primary.verifypeer = TRUE;
|
||||
set->ssl.primary.verifyhost = TRUE;
|
||||
#ifdef USE_TLS_SRP
|
||||
set->ssl.authtype = CURL_TLSAUTH_NONE;
|
||||
#endif
|
||||
set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth
|
||||
type */
|
||||
set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
|
||||
default */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
set->proxy_ssl = set->ssl;
|
||||
#endif
|
||||
|
||||
set->new_file_perms = 0644; /* Default permissions */
|
||||
set->new_directory_perms = 0755; /* Default permissions */
|
||||
|
||||
/* for the *protocols fields we don't use the CURLPROTO_ALL convenience
|
||||
define since we internally only use the lower 16 bits for the passed
|
||||
in bitmask to not conflict with the private bits */
|
||||
set->allowed_protocols = CURLPROTO_ALL;
|
||||
set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
|
||||
CURLPROTO_FTPS;
|
||||
|
||||
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
|
||||
/*
|
||||
* disallow unprotected protection negotiation NEC reference implementation
|
||||
* seem not to follow rfc1961 section 4.3/4.4
|
||||
*/
|
||||
set->socks5_gssapi_nec = FALSE;
|
||||
#endif
|
||||
|
||||
/* Set the default CA cert bundle/path detected/specified at build time.
|
||||
*
|
||||
* If Schannel is the selected SSL backend then these locations are
|
||||
* ignored. We allow setting CA location for schannel only when explicitly
|
||||
* specified by the user via CURLOPT_CAINFO / --cacert.
|
||||
*/
|
||||
if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) {
|
||||
#if defined(CURL_CA_BUNDLE)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_ORIG], CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY],
|
||||
CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
#if defined(CURL_CA_PATH)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_ORIG], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
set->wildcard_enabled = FALSE;
|
||||
set->chunk_bgn = ZERO_NULL;
|
||||
set->chunk_end = ZERO_NULL;
|
||||
set->tcp_keepalive = FALSE;
|
||||
set->tcp_keepintvl = 60;
|
||||
set->tcp_keepidle = 60;
|
||||
set->tcp_fastopen = FALSE;
|
||||
set->tcp_nodelay = TRUE;
|
||||
set->ssl_enable_npn = TRUE;
|
||||
set->ssl_enable_alpn = TRUE;
|
||||
set->expect_100_timeout = 1000L; /* Wait for a second by default. */
|
||||
set->sep_headers = TRUE; /* separated header lists by default */
|
||||
set->buffer_size = READBUFFER_SIZE;
|
||||
set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
|
||||
set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
|
||||
set->fnmatch = ZERO_NULL;
|
||||
set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
|
||||
set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
|
||||
set->maxage_conn = 118;
|
||||
set->http09_allowed = FALSE;
|
||||
set->httpversion =
|
||||
#ifdef USE_NGHTTP2
|
||||
CURL_HTTP_VERSION_2TLS
|
||||
#else
|
||||
CURL_HTTP_VERSION_1_1
|
||||
#endif
|
||||
;
|
||||
Curl_http2_init_userset(set);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/* Provenance: curl/curl verifystatus (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: d9d01672785b8ac04aab1abb6de95fe3072ae199
|
||||
* parent: 69a358f2186e04cf44698b5100332cbf1ee7f01d
|
||||
* commit_url: https://github.com/curl/curl/commit/d9d01672785b8ac04aab1abb6de95fe3072ae199
|
||||
* cve: CVE-2020-8286
|
||||
* license: curl
|
||||
* function: verifystatus
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
static CURLcode verifystatus(struct connectdata *conn,
|
||||
struct ssl_connect_data *connssl)
|
||||
{
|
||||
int i, ocsp_status;
|
||||
unsigned char *status;
|
||||
const unsigned char *p;
|
||||
CURLcode result = CURLE_OK;
|
||||
struct Curl_easy *data = conn->data;
|
||||
OCSP_RESPONSE *rsp = NULL;
|
||||
OCSP_BASICRESP *br = NULL;
|
||||
X509_STORE *st = NULL;
|
||||
STACK_OF(X509) *ch = NULL;
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
X509 *cert;
|
||||
OCSP_CERTID *id = NULL;
|
||||
int cert_status, crl_reason;
|
||||
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
|
||||
int ret;
|
||||
|
||||
long len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status);
|
||||
|
||||
if(!status) {
|
||||
failf(data, "No OCSP response received");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
p = status;
|
||||
rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
|
||||
if(!rsp) {
|
||||
failf(data, "Invalid OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ocsp_status = OCSP_response_status(rsp);
|
||||
if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
failf(data, "Invalid OCSP response status: %s (%d)",
|
||||
OCSP_response_status_str(ocsp_status), ocsp_status);
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
br = OCSP_response_get1_basic(rsp);
|
||||
if(!br) {
|
||||
failf(data, "Invalid OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ch = SSL_get_peer_cert_chain(backend->handle);
|
||||
st = SSL_CTX_get_cert_store(backend->ctx);
|
||||
|
||||
#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \
|
||||
(defined(LIBRESSL_VERSION_NUMBER) && \
|
||||
LIBRESSL_VERSION_NUMBER <= 0x2040200fL))
|
||||
/* The authorized responder cert in the OCSP response MUST be signed by the
|
||||
peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert,
|
||||
no problem, but if it's an intermediate cert OpenSSL has a bug where it
|
||||
expects this issuer to be present in the chain embedded in the OCSP
|
||||
response. So we add it if necessary. */
|
||||
|
||||
/* First make sure the peer cert chain includes both a peer and an issuer,
|
||||
and the OCSP response contains a responder cert. */
|
||||
if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) {
|
||||
X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1);
|
||||
|
||||
/* Find issuer of responder cert and add it to the OCSP response chain */
|
||||
for(i = 0; i < sk_X509_num(ch); i++) {
|
||||
X509 *issuer = sk_X509_value(ch, i);
|
||||
if(X509_check_issued(issuer, responder) == X509_V_OK) {
|
||||
if(!OCSP_basic_add1_cert(br, issuer)) {
|
||||
failf(data, "Could not add issuer cert to OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(OCSP_basic_verify(br, ch, st, 0) <= 0) {
|
||||
failf(data, "OCSP response verification failed");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Compute the certificate's ID */
|
||||
cert = SSL_get_peer_certificate(backend->handle);
|
||||
if(!cert) {
|
||||
failf(data, "Error getting peer certficate");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
for(i = 0; i < sk_X509_num(ch); i++) {
|
||||
X509 *issuer = sk_X509_value(ch, i);
|
||||
if(X509_check_issued(issuer, cert) == X509_V_OK) {
|
||||
id = OCSP_cert_to_id(EVP_sha1(), cert, issuer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
X509_free(cert);
|
||||
|
||||
if(!id) {
|
||||
failf(data, "Error computing OCSP ID");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Find the single OCSP response corresponding to the certificate ID */
|
||||
ret = OCSP_resp_find_status(br, id, &cert_status, &crl_reason, &rev,
|
||||
&thisupd, &nextupd);
|
||||
OCSP_CERTID_free(id);
|
||||
if(ret != 1) {
|
||||
failf(data, "Could not find certificate ID in OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Validate the corresponding single OCSP response */
|
||||
if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) {
|
||||
failf(data, "OCSP response has expired");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
infof(data, "SSL certificate status: %s (%d)\n",
|
||||
OCSP_cert_status_str(cert_status), cert_status);
|
||||
|
||||
switch(cert_status) {
|
||||
case V_OCSP_CERTSTATUS_GOOD:
|
||||
break;
|
||||
|
||||
case V_OCSP_CERTSTATUS_REVOKED:
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
failf(data, "SSL certificate revocation reason: %s (%d)",
|
||||
OCSP_crl_reason_str(crl_reason), crl_reason);
|
||||
goto end;
|
||||
|
||||
case V_OCSP_CERTSTATUS_UNKNOWN:
|
||||
default:
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
if(br)
|
||||
OCSP_BASICRESP_free(br);
|
||||
OCSP_RESPONSE_free(rsp);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/* Provenance: curl/curl verifystatus (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 69a358f2186e04cf44698b5100332cbf1ee7f01d
|
||||
* parent: 69a358f2186e04cf44698b5100332cbf1ee7f01d
|
||||
* commit_url: https://github.com/curl/curl/commit/d9d01672785b8ac04aab1abb6de95fe3072ae199
|
||||
* cve: CVE-2020-8286
|
||||
* license: curl
|
||||
* function: verifystatus
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
static CURLcode verifystatus(struct connectdata *conn,
|
||||
struct ssl_connect_data *connssl)
|
||||
{
|
||||
int i, ocsp_status;
|
||||
unsigned char *status;
|
||||
const unsigned char *p;
|
||||
CURLcode result = CURLE_OK;
|
||||
struct Curl_easy *data = conn->data;
|
||||
OCSP_RESPONSE *rsp = NULL;
|
||||
OCSP_BASICRESP *br = NULL;
|
||||
X509_STORE *st = NULL;
|
||||
STACK_OF(X509) *ch = NULL;
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
|
||||
long len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status);
|
||||
|
||||
if(!status) {
|
||||
failf(data, "No OCSP response received");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
p = status;
|
||||
rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
|
||||
if(!rsp) {
|
||||
failf(data, "Invalid OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ocsp_status = OCSP_response_status(rsp);
|
||||
if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
failf(data, "Invalid OCSP response status: %s (%d)",
|
||||
OCSP_response_status_str(ocsp_status), ocsp_status);
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
br = OCSP_response_get1_basic(rsp);
|
||||
if(!br) {
|
||||
failf(data, "Invalid OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ch = SSL_get_peer_cert_chain(backend->handle);
|
||||
st = SSL_CTX_get_cert_store(backend->ctx);
|
||||
|
||||
#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \
|
||||
(defined(LIBRESSL_VERSION_NUMBER) && \
|
||||
LIBRESSL_VERSION_NUMBER <= 0x2040200fL))
|
||||
/* The authorized responder cert in the OCSP response MUST be signed by the
|
||||
peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert,
|
||||
no problem, but if it's an intermediate cert OpenSSL has a bug where it
|
||||
expects this issuer to be present in the chain embedded in the OCSP
|
||||
response. So we add it if necessary. */
|
||||
|
||||
/* First make sure the peer cert chain includes both a peer and an issuer,
|
||||
and the OCSP response contains a responder cert. */
|
||||
if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) {
|
||||
X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1);
|
||||
|
||||
/* Find issuer of responder cert and add it to the OCSP response chain */
|
||||
for(i = 0; i < sk_X509_num(ch); i++) {
|
||||
X509 *issuer = sk_X509_value(ch, i);
|
||||
if(X509_check_issued(issuer, responder) == X509_V_OK) {
|
||||
if(!OCSP_basic_add1_cert(br, issuer)) {
|
||||
failf(data, "Could not add issuer cert to OCSP response");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(OCSP_basic_verify(br, ch, st, 0) <= 0) {
|
||||
failf(data, "OCSP response verification failed");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
for(i = 0; i < OCSP_resp_count(br); i++) {
|
||||
int cert_status, crl_reason;
|
||||
OCSP_SINGLERESP *single = NULL;
|
||||
|
||||
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
|
||||
|
||||
single = OCSP_resp_get0(br, i);
|
||||
if(!single)
|
||||
continue;
|
||||
|
||||
cert_status = OCSP_single_get0_status(single, &crl_reason, &rev,
|
||||
&thisupd, &nextupd);
|
||||
|
||||
if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) {
|
||||
failf(data, "OCSP response has expired");
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
infof(data, "SSL certificate status: %s (%d)\n",
|
||||
OCSP_cert_status_str(cert_status), cert_status);
|
||||
|
||||
switch(cert_status) {
|
||||
case V_OCSP_CERTSTATUS_GOOD:
|
||||
break;
|
||||
|
||||
case V_OCSP_CERTSTATUS_REVOKED:
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
|
||||
failf(data, "SSL certificate revocation reason: %s (%d)",
|
||||
OCSP_crl_reason_str(crl_reason), crl_reason);
|
||||
goto end;
|
||||
|
||||
case V_OCSP_CERTSTATUS_UNKNOWN:
|
||||
result = CURLE_SSL_INVALIDCERTSTATUS;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
if(br)
|
||||
OCSP_BASICRESP_free(br);
|
||||
OCSP_RESPONSE_free(rsp);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/* Provenance: curl/curl Curl_follow (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 7214288898f5625a6cc196e22a74232eada7861c
|
||||
* parent: 184ffc0bdfcab17eb96d1dd64f9c0cecc139e3e9
|
||||
* commit_url: https://github.com/curl/curl/commit/7214288898f5625a6cc196e22a74232eada7861c
|
||||
* cve: CVE-2021-22876
|
||||
* license: curl
|
||||
* function: Curl_follow
|
||||
* relpath: lib/transfer.c
|
||||
*/
|
||||
|
||||
* Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string
|
||||
* as given by the remote server and set up the new URL to request.
|
||||
*
|
||||
* This function DOES NOT FREE the given url.
|
||||
*/
|
||||
CURLcode Curl_follow(struct Curl_easy *data,
|
||||
char *newurl, /* the Location: string */
|
||||
followtype type) /* see transfer.h */
|
||||
{
|
||||
#ifdef CURL_DISABLE_HTTP
|
||||
(void)data;
|
||||
(void)newurl;
|
||||
(void)type;
|
||||
/* Location: following will not happen when HTTP is disabled */
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
#else
|
||||
|
||||
/* Location: redirect */
|
||||
bool disallowport = FALSE;
|
||||
bool reachedmax = FALSE;
|
||||
CURLUcode uc;
|
||||
|
||||
DEBUGASSERT(type != FOLLOW_NONE);
|
||||
|
||||
if(type == FOLLOW_REDIR) {
|
||||
if((data->set.maxredirs != -1) &&
|
||||
(data->state.followlocation >= data->set.maxredirs)) {
|
||||
reachedmax = TRUE;
|
||||
type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
|
||||
to URL */
|
||||
}
|
||||
else {
|
||||
/* mark the next request as a followed location: */
|
||||
data->state.this_is_a_follow = TRUE;
|
||||
|
||||
data->state.followlocation++; /* count location-followers */
|
||||
|
||||
if(data->set.http_auto_referer) {
|
||||
CURLU *u;
|
||||
char *referer;
|
||||
|
||||
/* We are asked to automatically set the previous URL as the referer
|
||||
when we get the next URL. We pick the ->url field, which may or may
|
||||
not be 100% correct */
|
||||
|
||||
if(data->state.referer_alloc) {
|
||||
Curl_safefree(data->state.referer);
|
||||
data->state.referer_alloc = FALSE;
|
||||
}
|
||||
|
||||
/* Make a copy of the URL without crenditals and fragment */
|
||||
u = curl_url();
|
||||
if(!u)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
|
||||
|
||||
curl_url_cleanup(u);
|
||||
|
||||
if(uc || referer == NULL)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
data->state.referer = referer;
|
||||
data->state.referer_alloc = TRUE; /* yes, free this later */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((type != FOLLOW_RETRY) &&
|
||||
(data->req.httpcode != 401) && (data->req.httpcode != 407) &&
|
||||
Curl_is_absolute_url(newurl, NULL, MAX_SCHEME_LEN))
|
||||
/* If this is not redirect due to a 401 or 407 response and an absolute
|
||||
URL: don't allow a custom port number */
|
||||
disallowport = TRUE;
|
||||
|
||||
DEBUGASSERT(data->state.uh);
|
||||
uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl,
|
||||
(type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
|
||||
((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) );
|
||||
if(uc) {
|
||||
if(type != FOLLOW_FAKE)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
|
||||
/* the URL could not be parsed for some reason, but since this is FAKE
|
||||
mode, just duplicate the field as-is */
|
||||
newurl = strdup(newurl);
|
||||
if(!newurl)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
else {
|
||||
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0);
|
||||
if(uc)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
}
|
||||
|
||||
if(type == FOLLOW_FAKE) {
|
||||
/* we're only figuring out the new url if we would've followed locations
|
||||
but now we're done so we can get out! */
|
||||
data->info.wouldredirect = newurl;
|
||||
|
||||
if(reachedmax) {
|
||||
failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs);
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(disallowport)
|
||||
data->state.allow_port = FALSE;
|
||||
|
||||
if(data->state.url_alloc)
|
||||
Curl_safefree(data->state.url);
|
||||
|
||||
data->state.url = newurl;
|
||||
data->state.url_alloc = TRUE;
|
||||
|
||||
infof(data, "Issue another request to this URL: '%s'\n", data->state.url);
|
||||
|
||||
/*
|
||||
* We get here when the HTTP code is 300-399 (and 401). We need to perform
|
||||
* differently based on exactly what return code there was.
|
||||
*
|
||||
* News from 7.10.6: we can also get here on a 401 or 407, in case we act on
|
||||
* a HTTP (proxy-) authentication scheme other than Basic.
|
||||
*/
|
||||
switch(data->info.httpcode) {
|
||||
/* 401 - Act on a WWW-Authenticate, we keep on moving and do the
|
||||
Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
|
||||
Proxy-Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 300 - Multiple Choices */
|
||||
/* 306 - Not used */
|
||||
/* 307 - Temporary Redirect */
|
||||
default: /* for all above (and the unknown ones) */
|
||||
/* Some codes are explicitly mentioned since I've checked RFC2616 and they
|
||||
* seem to be OK to POST to.
|
||||
*/
|
||||
break;
|
||||
case 301: /* Moved Permanently */
|
||||
/* (quote from RFC7231, section 6.4.2)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_301)) {
|
||||
infof(data, "Switch from POST to GET\n");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
case 302: /* Found */
|
||||
/* (quote from RFC7231, section 6.4.3)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_302)) {
|
||||
infof(data, "Switch from POST to GET\n");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 303: /* See Other */
|
||||
/* 'See Other' location is not the resource but a substitute for the
|
||||
* resource. In this case we switch the method to GET/HEAD, unless the
|
||||
* method is POST and the user specified to keep it as POST.
|
||||
* https://github.com/curl/curl/issues/5237#issuecomment-614641049
|
||||
*/
|
||||
if(data->state.httpreq != HTTPREQ_GET &&
|
||||
((data->state.httpreq != HTTPREQ_POST &&
|
||||
data->state.httpreq != HTTPREQ_POST_FORM &&
|
||||
data->state.httpreq != HTTPREQ_POST_MIME) ||
|
||||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
data->set.upload = false;
|
||||
infof(data, "Switch to %s\n",
|
||||
data->set.opt_no_body?"HEAD":"GET");
|
||||
}
|
||||
break;
|
||||
case 304: /* Not Modified */
|
||||
/* 304 means we did a conditional request and it was "Not modified".
|
||||
* We shouldn't get any Location: header in this response!
|
||||
*/
|
||||
break;
|
||||
case 305: /* Use Proxy */
|
||||
/* (quote from RFC2616, section 10.3.6):
|
||||
* "The requested resource MUST be accessed through the proxy given
|
||||
* by the Location field. The Location field gives the URI of the
|
||||
* proxy. The recipient is expected to repeat this single request
|
||||
* via the proxy. 305 responses MUST only be generated by origin
|
||||
* servers."
|
||||
*/
|
||||
break;
|
||||
}
|
||||
Curl_pgrsTime(data, TIMER_REDIRECT);
|
||||
Curl_pgrsResetTransferSizes(data);
|
||||
|
||||
return CURLE_OK;
|
||||
#endif /* CURL_DISABLE_HTTP */
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/* Provenance: curl/curl Curl_follow (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 184ffc0bdfcab17eb96d1dd64f9c0cecc139e3e9
|
||||
* parent: 184ffc0bdfcab17eb96d1dd64f9c0cecc139e3e9
|
||||
* commit_url: https://github.com/curl/curl/commit/7214288898f5625a6cc196e22a74232eada7861c
|
||||
* cve: CVE-2021-22876
|
||||
* license: curl
|
||||
* function: Curl_follow
|
||||
* relpath: lib/transfer.c
|
||||
*/
|
||||
|
||||
* Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string
|
||||
* as given by the remote server and set up the new URL to request.
|
||||
*
|
||||
* This function DOES NOT FREE the given url.
|
||||
*/
|
||||
CURLcode Curl_follow(struct Curl_easy *data,
|
||||
char *newurl, /* the Location: string */
|
||||
followtype type) /* see transfer.h */
|
||||
{
|
||||
#ifdef CURL_DISABLE_HTTP
|
||||
(void)data;
|
||||
(void)newurl;
|
||||
(void)type;
|
||||
/* Location: following will not happen when HTTP is disabled */
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
#else
|
||||
|
||||
/* Location: redirect */
|
||||
bool disallowport = FALSE;
|
||||
bool reachedmax = FALSE;
|
||||
CURLUcode uc;
|
||||
|
||||
DEBUGASSERT(type != FOLLOW_NONE);
|
||||
|
||||
if(type == FOLLOW_REDIR) {
|
||||
if((data->set.maxredirs != -1) &&
|
||||
(data->state.followlocation >= data->set.maxredirs)) {
|
||||
reachedmax = TRUE;
|
||||
type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
|
||||
to URL */
|
||||
}
|
||||
else {
|
||||
/* mark the next request as a followed location: */
|
||||
data->state.this_is_a_follow = TRUE;
|
||||
|
||||
data->state.followlocation++; /* count location-followers */
|
||||
|
||||
if(data->set.http_auto_referer) {
|
||||
/* We are asked to automatically set the previous URL as the referer
|
||||
when we get the next URL. We pick the ->url field, which may or may
|
||||
not be 100% correct */
|
||||
|
||||
if(data->state.referer_alloc) {
|
||||
Curl_safefree(data->state.referer);
|
||||
data->state.referer_alloc = FALSE;
|
||||
}
|
||||
|
||||
data->state.referer = strdup(data->state.url);
|
||||
if(!data->state.referer)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
data->state.referer_alloc = TRUE; /* yes, free this later */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((type != FOLLOW_RETRY) &&
|
||||
(data->req.httpcode != 401) && (data->req.httpcode != 407) &&
|
||||
Curl_is_absolute_url(newurl, NULL, MAX_SCHEME_LEN))
|
||||
/* If this is not redirect due to a 401 or 407 response and an absolute
|
||||
URL: don't allow a custom port number */
|
||||
disallowport = TRUE;
|
||||
|
||||
DEBUGASSERT(data->state.uh);
|
||||
uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl,
|
||||
(type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
|
||||
((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) );
|
||||
if(uc) {
|
||||
if(type != FOLLOW_FAKE)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
|
||||
/* the URL could not be parsed for some reason, but since this is FAKE
|
||||
mode, just duplicate the field as-is */
|
||||
newurl = strdup(newurl);
|
||||
if(!newurl)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
else {
|
||||
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0);
|
||||
if(uc)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
}
|
||||
|
||||
if(type == FOLLOW_FAKE) {
|
||||
/* we're only figuring out the new url if we would've followed locations
|
||||
but now we're done so we can get out! */
|
||||
data->info.wouldredirect = newurl;
|
||||
|
||||
if(reachedmax) {
|
||||
failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs);
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(disallowport)
|
||||
data->state.allow_port = FALSE;
|
||||
|
||||
if(data->state.url_alloc)
|
||||
Curl_safefree(data->state.url);
|
||||
|
||||
data->state.url = newurl;
|
||||
data->state.url_alloc = TRUE;
|
||||
|
||||
infof(data, "Issue another request to this URL: '%s'\n", data->state.url);
|
||||
|
||||
/*
|
||||
* We get here when the HTTP code is 300-399 (and 401). We need to perform
|
||||
* differently based on exactly what return code there was.
|
||||
*
|
||||
* News from 7.10.6: we can also get here on a 401 or 407, in case we act on
|
||||
* a HTTP (proxy-) authentication scheme other than Basic.
|
||||
*/
|
||||
switch(data->info.httpcode) {
|
||||
/* 401 - Act on a WWW-Authenticate, we keep on moving and do the
|
||||
Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
|
||||
Proxy-Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 300 - Multiple Choices */
|
||||
/* 306 - Not used */
|
||||
/* 307 - Temporary Redirect */
|
||||
default: /* for all above (and the unknown ones) */
|
||||
/* Some codes are explicitly mentioned since I've checked RFC2616 and they
|
||||
* seem to be OK to POST to.
|
||||
*/
|
||||
break;
|
||||
case 301: /* Moved Permanently */
|
||||
/* (quote from RFC7231, section 6.4.2)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_301)) {
|
||||
infof(data, "Switch from POST to GET\n");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
case 302: /* Found */
|
||||
/* (quote from RFC7231, section 6.4.3)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_302)) {
|
||||
infof(data, "Switch from POST to GET\n");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 303: /* See Other */
|
||||
/* 'See Other' location is not the resource but a substitute for the
|
||||
* resource. In this case we switch the method to GET/HEAD, unless the
|
||||
* method is POST and the user specified to keep it as POST.
|
||||
* https://github.com/curl/curl/issues/5237#issuecomment-614641049
|
||||
*/
|
||||
if(data->state.httpreq != HTTPREQ_GET &&
|
||||
((data->state.httpreq != HTTPREQ_POST &&
|
||||
data->state.httpreq != HTTPREQ_POST_FORM &&
|
||||
data->state.httpreq != HTTPREQ_POST_MIME) ||
|
||||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
data->set.upload = false;
|
||||
infof(data, "Switch to %s\n",
|
||||
data->set.opt_no_body?"HEAD":"GET");
|
||||
}
|
||||
break;
|
||||
case 304: /* Not Modified */
|
||||
/* 304 means we did a conditional request and it was "Not modified".
|
||||
* We shouldn't get any Location: header in this response!
|
||||
*/
|
||||
break;
|
||||
case 305: /* Use Proxy */
|
||||
/* (quote from RFC2616, section 10.3.6):
|
||||
* "The requested resource MUST be accessed through the proxy given
|
||||
* by the Location field. The Location field gives the URI of the
|
||||
* proxy. The recipient is expected to repeat this single request
|
||||
* via the proxy. 305 responses MUST only be generated by origin
|
||||
* servers."
|
||||
*/
|
||||
break;
|
||||
}
|
||||
Curl_pgrsTime(data, TIMER_REDIRECT);
|
||||
Curl_pgrsResetTransferSizes(data);
|
||||
|
||||
return CURLE_OK;
|
||||
#endif /* CURL_DISABLE_HTTP */
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/* Provenance: curl/curl bearssl_connect_step1 (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: b09c8ee15771c614c4bf3ddac893cdb12187c844
|
||||
* parent: 7214288898f5625a6cc196e22a74232eada7861c
|
||||
* commit_url: https://github.com/curl/curl/commit/b09c8ee15771c614c4bf3ddac893cdb12187c844
|
||||
* cve: CVE-2021-22890
|
||||
* license: curl
|
||||
* function: bearssl_connect_step1
|
||||
* relpath: lib/vtls/bearssl.c
|
||||
*/
|
||||
|
||||
static CURLcode bearssl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
const char *hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
|
||||
conn->host.name;
|
||||
#else
|
||||
const char *hostname = conn->host.name;
|
||||
#endif
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const bool verifyhost = SSL_CONN_CONFIG(verifyhost);
|
||||
CURLcode ret;
|
||||
unsigned version_min, version_max;
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
|
||||
switch(SSL_CONN_CONFIG(version)) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "BearSSL does not support SSLv2");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "BearSSL does not support SSLv3");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
version_min = BR_TLS10;
|
||||
version_max = BR_TLS10;
|
||||
break;
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
version_min = BR_TLS11;
|
||||
version_max = BR_TLS11;
|
||||
break;
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
version_min = BR_TLS12;
|
||||
version_max = BR_TLS12;
|
||||
break;
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
version_min = BR_TLS10;
|
||||
version_max = BR_TLS12;
|
||||
break;
|
||||
default:
|
||||
failf(data, "BearSSL: unknown CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(ssl_cafile) {
|
||||
ret = load_cafile(ssl_cafile, &backend->anchors, &backend->anchors_len);
|
||||
if(ret != CURLE_OK) {
|
||||
if(verifypeer) {
|
||||
failf(data, "error setting certificate verify locations."
|
||||
" CAfile: %s", ssl_cafile);
|
||||
return ret;
|
||||
}
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway:\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize SSL context */
|
||||
br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal,
|
||||
backend->anchors, backend->anchors_len);
|
||||
br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max);
|
||||
br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf,
|
||||
sizeof(backend->buf), 1);
|
||||
|
||||
/* initialize X.509 context */
|
||||
backend->x509.vtable = &x509_vtable;
|
||||
backend->x509.verifypeer = verifypeer;
|
||||
backend->x509.verifyhost = verifyhost;
|
||||
br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable);
|
||||
|
||||
if(SSL_SET_OPTION(primary.sessionid)) {
|
||||
void *session;
|
||||
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
|
||||
&session, NULL, sockindex)) {
|
||||
br_ssl_engine_set_session_parameters(&backend->ctx.eng, session);
|
||||
infof(data, "BearSSL: re-using session ID\n");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
|
||||
/* NOTE: when adding more protocols here, increase the size of the
|
||||
* protocols array in `struct ssl_backend_data`.
|
||||
*/
|
||||
|
||||
#ifdef USE_NGHTTP2
|
||||
if(data->state.httpversion >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
backend->protocols[cur++] = NGHTTP2_PROTO_VERSION_ID;
|
||||
infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID);
|
||||
}
|
||||
#endif
|
||||
|
||||
backend->protocols[cur++] = ALPN_HTTP_1_1;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1);
|
||||
|
||||
br_ssl_engine_set_protocol_names(&backend->ctx.eng,
|
||||
backend->protocols, cur);
|
||||
}
|
||||
|
||||
if((1 == Curl_inet_pton(AF_INET, hostname, &addr))
|
||||
#ifdef ENABLE_IPV6
|
||||
|| (1 == Curl_inet_pton(AF_INET6, hostname, &addr))
|
||||
#endif
|
||||
) {
|
||||
if(verifyhost) {
|
||||
failf(data, "BearSSL: "
|
||||
"host verification of IP address is not supported");
|
||||
return CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
hostname = NULL;
|
||||
}
|
||||
|
||||
if(!br_ssl_client_reset(&backend->ctx, hostname, 0))
|
||||
return CURLE_FAILED_INIT;
|
||||
backend->active = TRUE;
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/* Provenance: curl/curl bearssl_connect_step1 (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7214288898f5625a6cc196e22a74232eada7861c
|
||||
* parent: 7214288898f5625a6cc196e22a74232eada7861c
|
||||
* commit_url: https://github.com/curl/curl/commit/b09c8ee15771c614c4bf3ddac893cdb12187c844
|
||||
* cve: CVE-2021-22890
|
||||
* license: curl
|
||||
* function: bearssl_connect_step1
|
||||
* relpath: lib/vtls/bearssl.c
|
||||
*/
|
||||
|
||||
static CURLcode bearssl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
const char *hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
|
||||
conn->host.name;
|
||||
#else
|
||||
const char *hostname = conn->host.name;
|
||||
#endif
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const bool verifyhost = SSL_CONN_CONFIG(verifyhost);
|
||||
CURLcode ret;
|
||||
unsigned version_min, version_max;
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
|
||||
switch(SSL_CONN_CONFIG(version)) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "BearSSL does not support SSLv2");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "BearSSL does not support SSLv3");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
version_min = BR_TLS10;
|
||||
version_max = BR_TLS10;
|
||||
break;
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
version_min = BR_TLS11;
|
||||
version_max = BR_TLS11;
|
||||
break;
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
version_min = BR_TLS12;
|
||||
version_max = BR_TLS12;
|
||||
break;
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
version_min = BR_TLS10;
|
||||
version_max = BR_TLS12;
|
||||
break;
|
||||
default:
|
||||
failf(data, "BearSSL: unknown CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(ssl_cafile) {
|
||||
ret = load_cafile(ssl_cafile, &backend->anchors, &backend->anchors_len);
|
||||
if(ret != CURLE_OK) {
|
||||
if(verifypeer) {
|
||||
failf(data, "error setting certificate verify locations."
|
||||
" CAfile: %s", ssl_cafile);
|
||||
return ret;
|
||||
}
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway:\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize SSL context */
|
||||
br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal,
|
||||
backend->anchors, backend->anchors_len);
|
||||
br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max);
|
||||
br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf,
|
||||
sizeof(backend->buf), 1);
|
||||
|
||||
/* initialize X.509 context */
|
||||
backend->x509.vtable = &x509_vtable;
|
||||
backend->x509.verifypeer = verifypeer;
|
||||
backend->x509.verifyhost = verifyhost;
|
||||
br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable);
|
||||
|
||||
if(SSL_SET_OPTION(primary.sessionid)) {
|
||||
void *session;
|
||||
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, &session, NULL, sockindex)) {
|
||||
br_ssl_engine_set_session_parameters(&backend->ctx.eng, session);
|
||||
infof(data, "BearSSL: re-using session ID\n");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
|
||||
/* NOTE: when adding more protocols here, increase the size of the
|
||||
* protocols array in `struct ssl_backend_data`.
|
||||
*/
|
||||
|
||||
#ifdef USE_NGHTTP2
|
||||
if(data->state.httpversion >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
backend->protocols[cur++] = NGHTTP2_PROTO_VERSION_ID;
|
||||
infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID);
|
||||
}
|
||||
#endif
|
||||
|
||||
backend->protocols[cur++] = ALPN_HTTP_1_1;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1);
|
||||
|
||||
br_ssl_engine_set_protocol_names(&backend->ctx.eng,
|
||||
backend->protocols, cur);
|
||||
}
|
||||
|
||||
if((1 == Curl_inet_pton(AF_INET, hostname, &addr))
|
||||
#ifdef ENABLE_IPV6
|
||||
|| (1 == Curl_inet_pton(AF_INET6, hostname, &addr))
|
||||
#endif
|
||||
) {
|
||||
if(verifyhost) {
|
||||
failf(data, "BearSSL: "
|
||||
"host verification of IP address is not supported");
|
||||
return CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
hostname = NULL;
|
||||
}
|
||||
|
||||
if(!br_ssl_client_reset(&backend->ctx, hostname, 0))
|
||||
return CURLE_FAILED_INIT;
|
||||
backend->active = TRUE;
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Provenance: curl/curl suboption (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* parent: bbb71507b7bab52002f9b1e0880bed6a32834511
|
||||
* commit_url: https://github.com/curl/curl/commit/39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* cve: CVE-2021-22898
|
||||
* license: curl
|
||||
* function: suboption
|
||||
* relpath: lib/telnet.c
|
||||
*/
|
||||
|
||||
* suboption()
|
||||
*
|
||||
* Look at the sub-option buffer, and try to be helpful to the other
|
||||
* side.
|
||||
*/
|
||||
|
||||
static void suboption(struct Curl_easy *data)
|
||||
{
|
||||
struct curl_slist *v;
|
||||
unsigned char temp[2048];
|
||||
ssize_t bytes_written;
|
||||
size_t len;
|
||||
int err;
|
||||
char varname[128] = "";
|
||||
char varval[128] = "";
|
||||
struct TELNET *tn = data->req.p.telnet;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2);
|
||||
switch(CURL_SB_GET(tn)) {
|
||||
case CURL_TELOPT_TTYPE:
|
||||
len = strlen(tn->subopt_ttype) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE,
|
||||
CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_XDISPLOC:
|
||||
len = strlen(tn->subopt_xdisploc) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC,
|
||||
CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_NEW_ENVIRON:
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON,
|
||||
CURL_TELQUAL_IS);
|
||||
len = 4;
|
||||
|
||||
for(v = tn->telnet_vars; v; v = v->next) {
|
||||
size_t tmplen = (strlen(v->data) + 1);
|
||||
/* Add the variable only if it fits */
|
||||
if(len + tmplen < (int)sizeof(temp)-6) {
|
||||
if(sscanf(v->data, "%127[^,],%127s", varname, varval) == 2) {
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%s%c%s", CURL_NEW_ENV_VAR, varname,
|
||||
CURL_NEW_ENV_VALUE, varval);
|
||||
len += tmplen;
|
||||
}
|
||||
}
|
||||
}
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%c", CURL_IAC, CURL_SE);
|
||||
len += 2;
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Provenance: curl/curl suboption (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: bbb71507b7bab52002f9b1e0880bed6a32834511
|
||||
* parent: bbb71507b7bab52002f9b1e0880bed6a32834511
|
||||
* commit_url: https://github.com/curl/curl/commit/39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* cve: CVE-2021-22898
|
||||
* license: curl
|
||||
* function: suboption
|
||||
* relpath: lib/telnet.c
|
||||
*/
|
||||
|
||||
* suboption()
|
||||
*
|
||||
* Look at the sub-option buffer, and try to be helpful to the other
|
||||
* side.
|
||||
*/
|
||||
|
||||
static void suboption(struct Curl_easy *data)
|
||||
{
|
||||
struct curl_slist *v;
|
||||
unsigned char temp[2048];
|
||||
ssize_t bytes_written;
|
||||
size_t len;
|
||||
int err;
|
||||
char varname[128] = "";
|
||||
char varval[128] = "";
|
||||
struct TELNET *tn = data->req.p.telnet;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2);
|
||||
switch(CURL_SB_GET(tn)) {
|
||||
case CURL_TELOPT_TTYPE:
|
||||
len = strlen(tn->subopt_ttype) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE,
|
||||
CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_XDISPLOC:
|
||||
len = strlen(tn->subopt_xdisploc) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC,
|
||||
CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_NEW_ENVIRON:
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON,
|
||||
CURL_TELQUAL_IS);
|
||||
len = 4;
|
||||
|
||||
for(v = tn->telnet_vars; v; v = v->next) {
|
||||
size_t tmplen = (strlen(v->data) + 1);
|
||||
/* Add the variable only if it fits */
|
||||
if(len + tmplen < (int)sizeof(temp)-6) {
|
||||
if(sscanf(v->data, "%127[^,],%127s", varname, varval)) {
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%s%c%s", CURL_NEW_ENV_VAR, varname,
|
||||
CURL_NEW_ENV_VALUE, varval);
|
||||
len += tmplen;
|
||||
}
|
||||
}
|
||||
}
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%c", CURL_IAC, CURL_SE);
|
||||
len += 2;
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
/* Provenance: curl/curl ossl_connect_step1 (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 7f4a9a9b2a49547eae24d2e19bc5c346e9026479
|
||||
* parent: 39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* commit_url: https://github.com/curl/curl/commit/7f4a9a9b2a49547eae24d2e19bc5c346e9026479
|
||||
* cve: CVE-2021-22901
|
||||
* license: curl
|
||||
* function: ossl_connect_step1
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode ossl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
char *ciphers;
|
||||
SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
|
||||
X509_LOOKUP *lookup = NULL;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
ctx_option_t ctx_options = 0;
|
||||
void *ssl_sessionid = NULL;
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
bool sni;
|
||||
const char * const hostname = SSL_HOST_NAME();
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
#endif
|
||||
const long int ssl_version = SSL_CONN_CONFIG(version);
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(authtype);
|
||||
#endif
|
||||
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
|
||||
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
|
||||
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
|
||||
const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
|
||||
const char * const ssl_cafile =
|
||||
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
|
||||
(ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
|
||||
const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile);
|
||||
char error_buffer[256];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
bool imported_native_ca = false;
|
||||
|
||||
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
|
||||
|
||||
/* Make funny stuff to get random input */
|
||||
result = ossl_seed(data);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK;
|
||||
|
||||
/* check to see if we've been told to use an explicit SSL/TLS version */
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
case CURL_SSLVERSION_TLSv1_3:
|
||||
/* it will be handled later with the context options */
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
|
||||
req_method = TLS_client_method();
|
||||
#else
|
||||
req_method = SSLv23_client_method();
|
||||
#endif
|
||||
use_sni(TRUE);
|
||||
break;
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "No SSLv2 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "No SSLv3 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(backend->ctx)
|
||||
SSL_CTX_free(backend->ctx);
|
||||
backend->ctx = SSL_CTX_new(req_method);
|
||||
|
||||
if(!backend->ctx) {
|
||||
failf(data, "SSL: couldn't create a context: %s",
|
||||
ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTRL_SET_MSG_CALLBACK
|
||||
if(data->set.fdebug && data->set.verbose) {
|
||||
/* the SSL trace callback is only used for verbose logging */
|
||||
SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
|
||||
SSL_CTX_set_msg_callback_arg(backend->ctx, conn);
|
||||
set_logger(conn, data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* OpenSSL contains code to work-around lots of bugs and flaws in various
|
||||
SSL-implementations. SSL_CTX_set_options() is used to enabled those
|
||||
work-arounds. The man page for this option states that SSL_OP_ALL enables
|
||||
all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
|
||||
enable the bug workaround options if compatibility with somewhat broken
|
||||
implementations is desired."
|
||||
|
||||
The "-no_ticket" option was introduced in Openssl0.9.8j. It's a flag to
|
||||
disable "rfc4507bis session ticket support". rfc4507bis was later turned
|
||||
into the proper RFC5077 it seems: https://tools.ietf.org/html/rfc5077
|
||||
|
||||
The enabled extension concerns the session management. I wonder how often
|
||||
libcurl stops a connection and then resumes a TLS session. also, sending
|
||||
the session data is some overhead. .I suggest that you just use your
|
||||
proposed patch (which explicitly disables TICKET).
|
||||
|
||||
If someone writes an application with libcurl and openssl who wants to
|
||||
enable the feature, one can do this in the SSL callback.
|
||||
|
||||
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
|
||||
interoperability with web server Netscape Enterprise Server 2.0.1 which
|
||||
was released back in 1996.
|
||||
|
||||
Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
|
||||
become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
|
||||
CVE-2010-4180 when using previous OpenSSL versions we no longer enable
|
||||
this option regardless of OpenSSL version and SSL_OP_ALL definition.
|
||||
|
||||
OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
|
||||
(https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
|
||||
SSL_OP_ALL that _disables_ that work-around despite the fact that
|
||||
SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
|
||||
keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
|
||||
must not be set.
|
||||
*/
|
||||
|
||||
ctx_options = SSL_OP_ALL;
|
||||
|
||||
#ifdef SSL_OP_NO_TICKET
|
||||
ctx_options |= SSL_OP_NO_TICKET;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
ctx_options |= SSL_OP_NO_COMPRESSION;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
||||
/* mitigate CVE-2010-4180 */
|
||||
ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
/* unless the user explicitly ask to allow the protocol vulnerability we
|
||||
use the work-around */
|
||||
if(!SSL_SET_OPTION(enable_beast))
|
||||
ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
||||
#endif
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
|
||||
/* "--tlsv<x.y>" options mean TLS >= version <x.y> */
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
|
||||
case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
|
||||
case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
|
||||
/* asking for any TLS version as the minimum, means no SSL versions
|
||||
allowed */
|
||||
ctx_options |= SSL_OP_NO_SSLv2;
|
||||
ctx_options |= SSL_OP_NO_SSLv3;
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
|
||||
result = set_ssl_version_min_max(backend->ctx, conn);
|
||||
#else
|
||||
result = set_ssl_version_min_max_legacy(&ctx_options, data, conn,
|
||||
sockindex);
|
||||
#endif
|
||||
if(result != CURLE_OK)
|
||||
return result;
|
||||
break;
|
||||
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(backend->ctx, ctx_options);
|
||||
|
||||
#ifdef HAS_NPN
|
||||
if(conn->bits.tls_enable_npn)
|
||||
SSL_CTX_set_next_proto_select_cb(backend->ctx, select_next_proto_cb, data);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ALPN
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_H2);
|
||||
}
|
||||
#endif
|
||||
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1);
|
||||
|
||||
/* expects length prefixed preference ordered list of protocols in wire
|
||||
* format
|
||||
*/
|
||||
if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
|
||||
failf(data, "Error setting ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
|
||||
if(!result &&
|
||||
!cert_stuff(data, backend->ctx,
|
||||
ssl_cert, ssl_cert_blob, ssl_cert_type,
|
||||
SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
|
||||
SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd)))
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
if(result)
|
||||
/* failf() is already done in cert_stuff() */
|
||||
return result;
|
||||
}
|
||||
|
||||
ciphers = SSL_CONN_CONFIG(cipher_list);
|
||||
if(!ciphers)
|
||||
ciphers = (char *)DEFAULT_CIPHER_SELECTION;
|
||||
if(ciphers) {
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
|
||||
failf(data, "failed setting cipher list: %s", ciphers);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "Cipher selection: %s\n", ciphers);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
|
||||
{
|
||||
char *ciphers13 = SSL_CONN_CONFIG(cipher_list13);
|
||||
if(ciphers13) {
|
||||
if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) {
|
||||
failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "TLS 1.3 cipher selection: %s\n", ciphers13);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
|
||||
/* OpenSSL 1.1.1 requires clients to opt-in for PHA */
|
||||
SSL_CTX_set_post_handshake_auth(backend->ctx, 1);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_EC_CURVES
|
||||
{
|
||||
char *curves = SSL_CONN_CONFIG(curves);
|
||||
if(curves) {
|
||||
if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
|
||||
failf(data, "failed setting curves list: '%s'", curves);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
if(ssl_authtype == CURL_TLSAUTH_SRP) {
|
||||
char * const ssl_username = SSL_SET_OPTION(username);
|
||||
|
||||
infof(data, "Using TLS-SRP username: %s\n", ssl_username);
|
||||
|
||||
if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) {
|
||||
failf(data, "Unable to set SRP user name");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CTX_set_srp_password(backend->ctx, SSL_SET_OPTION(password))) {
|
||||
failf(data, "failed setting SRP password");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CONN_CONFIG(cipher_list)) {
|
||||
infof(data, "Setting cipher list SRP\n");
|
||||
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) {
|
||||
failf(data, "failed setting SRP cipher list");
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(USE_WIN32_CRYPTO)
|
||||
/* Import certificates from the Windows root certificate store if requested.
|
||||
https://stackoverflow.com/questions/9507184/
|
||||
https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
|
||||
https://tools.ietf.org/html/rfc5280 */
|
||||
if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) &&
|
||||
(SSL_SET_OPTION(native_ca_store))) {
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
|
||||
|
||||
if(hStore) {
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
/* The array of enhanced key usage OIDs will vary per certificate and is
|
||||
declared outside of the loop so that rather than malloc/free each
|
||||
iteration we can grow it with realloc, when necessary. */
|
||||
CERT_ENHKEY_USAGE *enhkey_usage = NULL;
|
||||
DWORD enhkey_usage_size = 0;
|
||||
|
||||
/* This loop makes a best effort to import all valid certificates from
|
||||
the MS root store. If a certificate cannot be imported it is skipped.
|
||||
'result' is used to store only hard-fail conditions (such as out of
|
||||
memory) that cause an early break. */
|
||||
result = CURLE_OK;
|
||||
for(;;) {
|
||||
X509 *x509;
|
||||
FILETIME now;
|
||||
BYTE key_usage[2];
|
||||
DWORD req_size;
|
||||
const unsigned char *encoded_cert;
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
char cert_name[256];
|
||||
#endif
|
||||
|
||||
pContext = CertEnumCertificatesInStore(hStore, pContext);
|
||||
if(!pContext)
|
||||
break;
|
||||
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
|
||||
NULL, cert_name, sizeof(cert_name))) {
|
||||
strcpy(cert_name, "Unknown");
|
||||
}
|
||||
infof(data, "SSL: Checking cert \"%s\"\n", cert_name);
|
||||
#endif
|
||||
|
||||
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
|
||||
if(!encoded_cert)
|
||||
continue;
|
||||
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
|
||||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
|
||||
continue;
|
||||
|
||||
/* If key usage exists check for signing attribute */
|
||||
if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
|
||||
pContext->pCertInfo,
|
||||
key_usage, sizeof(key_usage))) {
|
||||
if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
|
||||
continue;
|
||||
}
|
||||
else if(GetLastError())
|
||||
continue;
|
||||
|
||||
/* If enhanced key usage exists check for server auth attribute.
|
||||
*
|
||||
* Note "In a Microsoft environment, a certificate might also have EKU
|
||||
* extended properties that specify valid uses for the certificate."
|
||||
* The call below checks both, and behavior varies depending on what is
|
||||
* found. For more details see CertGetEnhancedKeyUsage doc.
|
||||
*/
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
|
||||
if(req_size && req_size > enhkey_usage_size) {
|
||||
void *tmp = realloc(enhkey_usage, req_size);
|
||||
|
||||
if(!tmp) {
|
||||
failf(data, "SSL: Out of memory allocating for OID list");
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
|
||||
enhkey_usage_size = req_size;
|
||||
}
|
||||
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
|
||||
if(!enhkey_usage->cUsageIdentifier) {
|
||||
/* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
|
||||
good for all uses. If it returns zero, the certificate has no
|
||||
valid uses." */
|
||||
if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
DWORD i;
|
||||
bool found = false;
|
||||
|
||||
for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
|
||||
if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
|
||||
enhkey_usage->rgpszUsageIdentifier[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if(!x509)
|
||||
continue;
|
||||
|
||||
/* Try to import the certificate. This may fail for legitimate reasons
|
||||
such as duplicate certificate, which is allowed by MS but not
|
||||
OpenSSL. */
|
||||
if(X509_STORE_add_cert(store, x509) == 1) {
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
infof(data, "SSL: Imported cert \"%s\"\n", cert_name);
|
||||
#endif
|
||||
imported_native_ca = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported windows ca store\n");
|
||||
else
|
||||
infof(data, "error importing windows ca store, continuing anyway\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(backend->ctx, ca_info_blob);
|
||||
if(result) {
|
||||
if(result == CURLE_OUT_OF_MEMORY ||
|
||||
(verifypeer && !imported_native_ca)) {
|
||||
failf(data, "error importing CA certificate blob");
|
||||
return result;
|
||||
}
|
||||
/* Only warning if no certificate verification is required. */
|
||||
infof(data, "error importing CA certificate blob, continuing anyway\n");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
{
|
||||
if(ssl_cafile) {
|
||||
if(!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Continue with a warning if no certificate verif is required. */
|
||||
infof(data, "error setting certificate file, continuing anyway\n");
|
||||
}
|
||||
infof(data, " CAfile: %s\n", ssl_cafile);
|
||||
}
|
||||
if(ssl_capath) {
|
||||
if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Continue with a warning if no certificate verif is required. */
|
||||
infof(data, "error setting certificate path, continuing anyway\n");
|
||||
}
|
||||
infof(data, " CApath: %s\n", ssl_capath);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if(ssl_cafile || ssl_capath) {
|
||||
/* tell SSL where to find CA certificates that are used to verify
|
||||
the servers certificate. */
|
||||
if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Just continue with a warning if no strict certificate verification
|
||||
is required. */
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway:\n");
|
||||
}
|
||||
else {
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully set certificate verify locations:\n");
|
||||
}
|
||||
infof(data, " CAfile: %s\n", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s\n", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
if(verifypeer &&
|
||||
!ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built in default as fallback */
|
||||
SSL_CTX_set_default_verify_paths(backend->ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_crlfile) {
|
||||
/* tell SSL where to find CRL file that is used to check certificate
|
||||
* revocation */
|
||||
lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_LOOKUP_file());
|
||||
if(!lookup ||
|
||||
(!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
|
||||
failf(data, "error loading CRL file: %s", ssl_crlfile);
|
||||
return CURLE_SSL_CRL_BADFILE;
|
||||
}
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully load CRL file:\n");
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
|
||||
|
||||
infof(data, " CRLfile: %s\n", ssl_crlfile);
|
||||
}
|
||||
|
||||
if(verifypeer) {
|
||||
/* Try building a chain using issuers in the trusted store first to avoid
|
||||
problems with server-sent legacy intermediates. Newer versions of
|
||||
OpenSSL do alternate chain checking by default but we do not know how to
|
||||
determine that in a reliable manner.
|
||||
https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest
|
||||
*/
|
||||
#if defined(X509_V_FLAG_TRUSTED_FIRST)
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_TRUSTED_FIRST);
|
||||
#endif
|
||||
#ifdef X509_V_FLAG_PARTIAL_CHAIN
|
||||
if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) {
|
||||
/* Have intermediate certificates in the trust store be treated as
|
||||
trust-anchors, in the same way as self-signed root CA certificates
|
||||
are. This allows users to verify servers using the intermediate cert
|
||||
only, instead of needing the whole chain.
|
||||
|
||||
Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we
|
||||
cannot do partial chains with CRL check.
|
||||
*/
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_PARTIAL_CHAIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* SSL always tries to verify the peer, this only says whether it should
|
||||
* fail to connect if the verification fails, or if it should continue
|
||||
* anyway. In the latter case the result of the verification is checked with
|
||||
* SSL_get_verify_result() below. */
|
||||
SSL_CTX_set_verify(backend->ctx,
|
||||
verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
||||
|
||||
/* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
|
||||
#ifdef HAVE_KEYLOG_CALLBACK
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Enable the session cache because it's a prerequisite for the "new session"
|
||||
* callback. Use the "external storage" mode to avoid that OpenSSL creates
|
||||
* an internal session cache.
|
||||
*/
|
||||
SSL_CTX_set_session_cache_mode(backend->ctx,
|
||||
SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
|
||||
SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb);
|
||||
|
||||
/* give application a chance to interfere with SSL set up. */
|
||||
if(data->set.ssl.fsslctx) {
|
||||
Curl_set_in_callback(data, true);
|
||||
result = (*data->set.ssl.fsslctx)(data, backend->ctx,
|
||||
data->set.ssl.fsslctxp);
|
||||
Curl_set_in_callback(data, false);
|
||||
if(result) {
|
||||
failf(data, "error signaled by ssl ctx callback");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lets make an SSL structure */
|
||||
if(backend->handle)
|
||||
SSL_free(backend->handle);
|
||||
backend->handle = SSL_new(backend->ctx);
|
||||
if(!backend->handle) {
|
||||
failf(data, "SSL: couldn't create a context (handle)!");
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus))
|
||||
SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG)
|
||||
SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely);
|
||||
#endif
|
||||
|
||||
SSL_set_connect_state(backend->handle);
|
||||
|
||||
backend->server_cert = 0x0;
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
|
||||
#ifdef ENABLE_IPV6
|
||||
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
|
||||
#endif
|
||||
sni) {
|
||||
size_t nlen = strlen(hostname);
|
||||
if((long)nlen >= data->set.buffer_size)
|
||||
/* this is seriously messed up */
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
|
||||
/* RFC 6066 section 3 says the SNI field is case insensitive, but browsers
|
||||
send the data lowercase and subsequently there are now numerous servers
|
||||
out there that don't work unless the name is lowercased */
|
||||
Curl_strntolower(data->state.buffer, hostname, nlen);
|
||||
data->state.buffer[nlen] = 0;
|
||||
if(!SSL_set_tlsext_host_name(backend->handle, data->state.buffer))
|
||||
infof(data, "WARNING: failed to configure server name indication (SNI) "
|
||||
"TLS extension\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
ossl_associate_connection(data, conn, sockindex);
|
||||
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
|
||||
&ssl_sessionid, NULL, sockindex)) {
|
||||
/* we got a session id, use it! */
|
||||
if(!SSL_set_session(backend->handle, ssl_sessionid)) {
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
failf(data, "SSL: SSL_set_session failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
/* Informational message */
|
||||
infof(data, "SSL re-using session ID\n");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
BIO *const bio = BIO_new(BIO_f_ssl());
|
||||
SSL *handle = conn->proxy_ssl[sockindex].backend->handle;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(handle != NULL);
|
||||
DEBUGASSERT(bio != NULL);
|
||||
BIO_set_ssl(bio, handle, FALSE);
|
||||
SSL_set_bio(backend->handle, bio, bio);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!SSL_set_fd(backend->handle, (int)sockfd)) {
|
||||
/* pass the raw socket into the SSL layers */
|
||||
failf(data, "SSL: SSL_set_fd failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,729 @@
|
||||
/* Provenance: curl/curl ossl_connect_step1 (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* parent: 39ce47f219b09c380b81f89fe54ac586c8db6bde
|
||||
* commit_url: https://github.com/curl/curl/commit/7f4a9a9b2a49547eae24d2e19bc5c346e9026479
|
||||
* cve: CVE-2021-22901
|
||||
* license: curl
|
||||
* function: ossl_connect_step1
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode ossl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
char *ciphers;
|
||||
SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
|
||||
X509_LOOKUP *lookup = NULL;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
ctx_option_t ctx_options = 0;
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
bool sni;
|
||||
const char * const hostname = SSL_HOST_NAME();
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
#endif
|
||||
const long int ssl_version = SSL_CONN_CONFIG(version);
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(authtype);
|
||||
#endif
|
||||
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
|
||||
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
|
||||
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
|
||||
const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
|
||||
const char * const ssl_cafile =
|
||||
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
|
||||
(ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
|
||||
const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile);
|
||||
char error_buffer[256];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
bool imported_native_ca = false;
|
||||
|
||||
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
|
||||
|
||||
/* Make funny stuff to get random input */
|
||||
result = ossl_seed(data);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK;
|
||||
|
||||
/* check to see if we've been told to use an explicit SSL/TLS version */
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
case CURL_SSLVERSION_TLSv1_3:
|
||||
/* it will be handled later with the context options */
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
|
||||
req_method = TLS_client_method();
|
||||
#else
|
||||
req_method = SSLv23_client_method();
|
||||
#endif
|
||||
use_sni(TRUE);
|
||||
break;
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "No SSLv2 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "No SSLv3 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(backend->ctx)
|
||||
SSL_CTX_free(backend->ctx);
|
||||
backend->ctx = SSL_CTX_new(req_method);
|
||||
|
||||
if(!backend->ctx) {
|
||||
failf(data, "SSL: couldn't create a context: %s",
|
||||
ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTRL_SET_MSG_CALLBACK
|
||||
if(data->set.fdebug && data->set.verbose) {
|
||||
/* the SSL trace callback is only used for verbose logging */
|
||||
SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
|
||||
SSL_CTX_set_msg_callback_arg(backend->ctx, conn);
|
||||
set_logger(conn, data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* OpenSSL contains code to work-around lots of bugs and flaws in various
|
||||
SSL-implementations. SSL_CTX_set_options() is used to enabled those
|
||||
work-arounds. The man page for this option states that SSL_OP_ALL enables
|
||||
all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
|
||||
enable the bug workaround options if compatibility with somewhat broken
|
||||
implementations is desired."
|
||||
|
||||
The "-no_ticket" option was introduced in Openssl0.9.8j. It's a flag to
|
||||
disable "rfc4507bis session ticket support". rfc4507bis was later turned
|
||||
into the proper RFC5077 it seems: https://tools.ietf.org/html/rfc5077
|
||||
|
||||
The enabled extension concerns the session management. I wonder how often
|
||||
libcurl stops a connection and then resumes a TLS session. also, sending
|
||||
the session data is some overhead. .I suggest that you just use your
|
||||
proposed patch (which explicitly disables TICKET).
|
||||
|
||||
If someone writes an application with libcurl and openssl who wants to
|
||||
enable the feature, one can do this in the SSL callback.
|
||||
|
||||
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
|
||||
interoperability with web server Netscape Enterprise Server 2.0.1 which
|
||||
was released back in 1996.
|
||||
|
||||
Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
|
||||
become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
|
||||
CVE-2010-4180 when using previous OpenSSL versions we no longer enable
|
||||
this option regardless of OpenSSL version and SSL_OP_ALL definition.
|
||||
|
||||
OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
|
||||
(https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
|
||||
SSL_OP_ALL that _disables_ that work-around despite the fact that
|
||||
SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
|
||||
keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
|
||||
must not be set.
|
||||
*/
|
||||
|
||||
ctx_options = SSL_OP_ALL;
|
||||
|
||||
#ifdef SSL_OP_NO_TICKET
|
||||
ctx_options |= SSL_OP_NO_TICKET;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
ctx_options |= SSL_OP_NO_COMPRESSION;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
||||
/* mitigate CVE-2010-4180 */
|
||||
ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
/* unless the user explicitly ask to allow the protocol vulnerability we
|
||||
use the work-around */
|
||||
if(!SSL_SET_OPTION(enable_beast))
|
||||
ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
||||
#endif
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
|
||||
/* "--tlsv<x.y>" options mean TLS >= version <x.y> */
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
|
||||
case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
|
||||
case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
|
||||
/* asking for any TLS version as the minimum, means no SSL versions
|
||||
allowed */
|
||||
ctx_options |= SSL_OP_NO_SSLv2;
|
||||
ctx_options |= SSL_OP_NO_SSLv3;
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
|
||||
result = set_ssl_version_min_max(backend->ctx, conn);
|
||||
#else
|
||||
result = set_ssl_version_min_max_legacy(&ctx_options, data, conn,
|
||||
sockindex);
|
||||
#endif
|
||||
if(result != CURLE_OK)
|
||||
return result;
|
||||
break;
|
||||
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(backend->ctx, ctx_options);
|
||||
|
||||
#ifdef HAS_NPN
|
||||
if(conn->bits.tls_enable_npn)
|
||||
SSL_CTX_set_next_proto_select_cb(backend->ctx, select_next_proto_cb, data);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ALPN
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_H2);
|
||||
}
|
||||
#endif
|
||||
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1);
|
||||
|
||||
/* expects length prefixed preference ordered list of protocols in wire
|
||||
* format
|
||||
*/
|
||||
if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
|
||||
failf(data, "Error setting ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
|
||||
if(!result &&
|
||||
!cert_stuff(data, backend->ctx,
|
||||
ssl_cert, ssl_cert_blob, ssl_cert_type,
|
||||
SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
|
||||
SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd)))
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
if(result)
|
||||
/* failf() is already done in cert_stuff() */
|
||||
return result;
|
||||
}
|
||||
|
||||
ciphers = SSL_CONN_CONFIG(cipher_list);
|
||||
if(!ciphers)
|
||||
ciphers = (char *)DEFAULT_CIPHER_SELECTION;
|
||||
if(ciphers) {
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
|
||||
failf(data, "failed setting cipher list: %s", ciphers);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "Cipher selection: %s\n", ciphers);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
|
||||
{
|
||||
char *ciphers13 = SSL_CONN_CONFIG(cipher_list13);
|
||||
if(ciphers13) {
|
||||
if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) {
|
||||
failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "TLS 1.3 cipher selection: %s\n", ciphers13);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
|
||||
/* OpenSSL 1.1.1 requires clients to opt-in for PHA */
|
||||
SSL_CTX_set_post_handshake_auth(backend->ctx, 1);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_EC_CURVES
|
||||
{
|
||||
char *curves = SSL_CONN_CONFIG(curves);
|
||||
if(curves) {
|
||||
if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
|
||||
failf(data, "failed setting curves list: '%s'", curves);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
if(ssl_authtype == CURL_TLSAUTH_SRP) {
|
||||
char * const ssl_username = SSL_SET_OPTION(username);
|
||||
|
||||
infof(data, "Using TLS-SRP username: %s\n", ssl_username);
|
||||
|
||||
if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) {
|
||||
failf(data, "Unable to set SRP user name");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CTX_set_srp_password(backend->ctx, SSL_SET_OPTION(password))) {
|
||||
failf(data, "failed setting SRP password");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CONN_CONFIG(cipher_list)) {
|
||||
infof(data, "Setting cipher list SRP\n");
|
||||
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) {
|
||||
failf(data, "failed setting SRP cipher list");
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(USE_WIN32_CRYPTO)
|
||||
/* Import certificates from the Windows root certificate store if requested.
|
||||
https://stackoverflow.com/questions/9507184/
|
||||
https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
|
||||
https://tools.ietf.org/html/rfc5280 */
|
||||
if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) &&
|
||||
(SSL_SET_OPTION(native_ca_store))) {
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
|
||||
|
||||
if(hStore) {
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
/* The array of enhanced key usage OIDs will vary per certificate and is
|
||||
declared outside of the loop so that rather than malloc/free each
|
||||
iteration we can grow it with realloc, when necessary. */
|
||||
CERT_ENHKEY_USAGE *enhkey_usage = NULL;
|
||||
DWORD enhkey_usage_size = 0;
|
||||
|
||||
/* This loop makes a best effort to import all valid certificates from
|
||||
the MS root store. If a certificate cannot be imported it is skipped.
|
||||
'result' is used to store only hard-fail conditions (such as out of
|
||||
memory) that cause an early break. */
|
||||
result = CURLE_OK;
|
||||
for(;;) {
|
||||
X509 *x509;
|
||||
FILETIME now;
|
||||
BYTE key_usage[2];
|
||||
DWORD req_size;
|
||||
const unsigned char *encoded_cert;
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
char cert_name[256];
|
||||
#endif
|
||||
|
||||
pContext = CertEnumCertificatesInStore(hStore, pContext);
|
||||
if(!pContext)
|
||||
break;
|
||||
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
|
||||
NULL, cert_name, sizeof(cert_name))) {
|
||||
strcpy(cert_name, "Unknown");
|
||||
}
|
||||
infof(data, "SSL: Checking cert \"%s\"\n", cert_name);
|
||||
#endif
|
||||
|
||||
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
|
||||
if(!encoded_cert)
|
||||
continue;
|
||||
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
|
||||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
|
||||
continue;
|
||||
|
||||
/* If key usage exists check for signing attribute */
|
||||
if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
|
||||
pContext->pCertInfo,
|
||||
key_usage, sizeof(key_usage))) {
|
||||
if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
|
||||
continue;
|
||||
}
|
||||
else if(GetLastError())
|
||||
continue;
|
||||
|
||||
/* If enhanced key usage exists check for server auth attribute.
|
||||
*
|
||||
* Note "In a Microsoft environment, a certificate might also have EKU
|
||||
* extended properties that specify valid uses for the certificate."
|
||||
* The call below checks both, and behavior varies depending on what is
|
||||
* found. For more details see CertGetEnhancedKeyUsage doc.
|
||||
*/
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
|
||||
if(req_size && req_size > enhkey_usage_size) {
|
||||
void *tmp = realloc(enhkey_usage, req_size);
|
||||
|
||||
if(!tmp) {
|
||||
failf(data, "SSL: Out of memory allocating for OID list");
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
|
||||
enhkey_usage_size = req_size;
|
||||
}
|
||||
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
|
||||
if(!enhkey_usage->cUsageIdentifier) {
|
||||
/* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
|
||||
good for all uses. If it returns zero, the certificate has no
|
||||
valid uses." */
|
||||
if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
DWORD i;
|
||||
bool found = false;
|
||||
|
||||
for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
|
||||
if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
|
||||
enhkey_usage->rgpszUsageIdentifier[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if(!x509)
|
||||
continue;
|
||||
|
||||
/* Try to import the certificate. This may fail for legitimate reasons
|
||||
such as duplicate certificate, which is allowed by MS but not
|
||||
OpenSSL. */
|
||||
if(X509_STORE_add_cert(store, x509) == 1) {
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
infof(data, "SSL: Imported cert \"%s\"\n", cert_name);
|
||||
#endif
|
||||
imported_native_ca = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported windows ca store\n");
|
||||
else
|
||||
infof(data, "error importing windows ca store, continuing anyway\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(backend->ctx, ca_info_blob);
|
||||
if(result) {
|
||||
if(result == CURLE_OUT_OF_MEMORY ||
|
||||
(verifypeer && !imported_native_ca)) {
|
||||
failf(data, "error importing CA certificate blob");
|
||||
return result;
|
||||
}
|
||||
/* Only warning if no certificate verification is required. */
|
||||
infof(data, "error importing CA certificate blob, continuing anyway\n");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
{
|
||||
if(ssl_cafile) {
|
||||
if(!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Continue with a warning if no certificate verif is required. */
|
||||
infof(data, "error setting certificate file, continuing anyway\n");
|
||||
}
|
||||
infof(data, " CAfile: %s\n", ssl_cafile);
|
||||
}
|
||||
if(ssl_capath) {
|
||||
if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Continue with a warning if no certificate verif is required. */
|
||||
infof(data, "error setting certificate path, continuing anyway\n");
|
||||
}
|
||||
infof(data, " CApath: %s\n", ssl_capath);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if(ssl_cafile || ssl_capath) {
|
||||
/* tell SSL where to find CA certificates that are used to verify
|
||||
the servers certificate. */
|
||||
if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) {
|
||||
if(verifypeer && !imported_native_ca) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
/* Just continue with a warning if no strict certificate verification
|
||||
is required. */
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway:\n");
|
||||
}
|
||||
else {
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully set certificate verify locations:\n");
|
||||
}
|
||||
infof(data, " CAfile: %s\n", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s\n", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
if(verifypeer &&
|
||||
!ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built in default as fallback */
|
||||
SSL_CTX_set_default_verify_paths(backend->ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_crlfile) {
|
||||
/* tell SSL where to find CRL file that is used to check certificate
|
||||
* revocation */
|
||||
lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_LOOKUP_file());
|
||||
if(!lookup ||
|
||||
(!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
|
||||
failf(data, "error loading CRL file: %s", ssl_crlfile);
|
||||
return CURLE_SSL_CRL_BADFILE;
|
||||
}
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully load CRL file:\n");
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
|
||||
|
||||
infof(data, " CRLfile: %s\n", ssl_crlfile);
|
||||
}
|
||||
|
||||
if(verifypeer) {
|
||||
/* Try building a chain using issuers in the trusted store first to avoid
|
||||
problems with server-sent legacy intermediates. Newer versions of
|
||||
OpenSSL do alternate chain checking by default but we do not know how to
|
||||
determine that in a reliable manner.
|
||||
https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest
|
||||
*/
|
||||
#if defined(X509_V_FLAG_TRUSTED_FIRST)
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_TRUSTED_FIRST);
|
||||
#endif
|
||||
#ifdef X509_V_FLAG_PARTIAL_CHAIN
|
||||
if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) {
|
||||
/* Have intermediate certificates in the trust store be treated as
|
||||
trust-anchors, in the same way as self-signed root CA certificates
|
||||
are. This allows users to verify servers using the intermediate cert
|
||||
only, instead of needing the whole chain.
|
||||
|
||||
Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we
|
||||
cannot do partial chains with CRL check.
|
||||
*/
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_PARTIAL_CHAIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* SSL always tries to verify the peer, this only says whether it should
|
||||
* fail to connect if the verification fails, or if it should continue
|
||||
* anyway. In the latter case the result of the verification is checked with
|
||||
* SSL_get_verify_result() below. */
|
||||
SSL_CTX_set_verify(backend->ctx,
|
||||
verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
||||
|
||||
/* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
|
||||
#ifdef HAVE_KEYLOG_CALLBACK
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Enable the session cache because it's a prerequisite for the "new session"
|
||||
* callback. Use the "external storage" mode to avoid that OpenSSL creates
|
||||
* an internal session cache.
|
||||
*/
|
||||
SSL_CTX_set_session_cache_mode(backend->ctx,
|
||||
SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
|
||||
SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb);
|
||||
|
||||
/* give application a chance to interfere with SSL set up. */
|
||||
if(data->set.ssl.fsslctx) {
|
||||
Curl_set_in_callback(data, true);
|
||||
result = (*data->set.ssl.fsslctx)(data, backend->ctx,
|
||||
data->set.ssl.fsslctxp);
|
||||
Curl_set_in_callback(data, false);
|
||||
if(result) {
|
||||
failf(data, "error signaled by ssl ctx callback");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lets make an SSL structure */
|
||||
if(backend->handle)
|
||||
SSL_free(backend->handle);
|
||||
backend->handle = SSL_new(backend->ctx);
|
||||
if(!backend->handle) {
|
||||
failf(data, "SSL: couldn't create a context (handle)!");
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus))
|
||||
SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG)
|
||||
SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely);
|
||||
#endif
|
||||
|
||||
SSL_set_connect_state(backend->handle);
|
||||
|
||||
backend->server_cert = 0x0;
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
|
||||
#ifdef ENABLE_IPV6
|
||||
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
|
||||
#endif
|
||||
sni) {
|
||||
size_t nlen = strlen(hostname);
|
||||
if((long)nlen >= data->set.buffer_size)
|
||||
/* this is seriously messed up */
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
|
||||
/* RFC 6066 section 3 says the SNI field is case insensitive, but browsers
|
||||
send the data lowercase and subsequently there are now numerous servers
|
||||
out there that don't work unless the name is lowercased */
|
||||
Curl_strntolower(data->state.buffer, hostname, nlen);
|
||||
data->state.buffer[nlen] = 0;
|
||||
if(!SSL_set_tlsext_host_name(backend->handle, data->state.buffer))
|
||||
infof(data, "WARNING: failed to configure server name indication (SNI) "
|
||||
"TLS extension\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check if there's a cached ID we can/should use here! */
|
||||
if(SSL_SET_OPTION(primary.sessionid)) {
|
||||
void *ssl_sessionid = NULL;
|
||||
int data_idx = ossl_get_ssl_data_index();
|
||||
int connectdata_idx = ossl_get_ssl_conn_index();
|
||||
int sockindex_idx = ossl_get_ssl_sockindex_index();
|
||||
int proxy_idx = ossl_get_proxy_index();
|
||||
|
||||
if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 &&
|
||||
proxy_idx >= 0) {
|
||||
/* Store the data needed for the "new session" callback.
|
||||
* The sockindex is stored as a pointer to an array element. */
|
||||
SSL_set_ex_data(backend->handle, data_idx, data);
|
||||
SSL_set_ex_data(backend->handle, connectdata_idx, conn);
|
||||
SSL_set_ex_data(backend->handle, sockindex_idx, conn->sock + sockindex);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
SSL_set_ex_data(backend->handle, proxy_idx, SSL_IS_PROXY() ? (void *) 1:
|
||||
NULL);
|
||||
#else
|
||||
SSL_set_ex_data(backend->handle, proxy_idx, NULL);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
|
||||
&ssl_sessionid, NULL, sockindex)) {
|
||||
/* we got a session id, use it! */
|
||||
if(!SSL_set_session(backend->handle, ssl_sessionid)) {
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
failf(data, "SSL: SSL_set_session failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
/* Informational message */
|
||||
infof(data, "SSL re-using session ID\n");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
BIO *const bio = BIO_new(BIO_f_ssl());
|
||||
SSL *handle = conn->proxy_ssl[sockindex].backend->handle;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(handle != NULL);
|
||||
DEBUGASSERT(bio != NULL);
|
||||
BIO_set_ssl(bio, handle, FALSE);
|
||||
SSL_set_bio(backend->handle, bio, bio);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!SSL_set_fd(backend->handle, (int)sockfd)) {
|
||||
/* pass the raw socket into the SSL layers */
|
||||
failf(data, "SSL: SSL_set_fd failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* Provenance: curl/curl nss_do_connect (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: nss_do_connect
|
||||
* relpath: lib/vtls/nss.c
|
||||
*/
|
||||
|
||||
static CURLcode nss_do_connect(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
CURLcode result = CURLE_SSL_CONNECT_ERROR;
|
||||
PRUint32 timeout;
|
||||
|
||||
/* check timeout situation */
|
||||
const timediff_t time_left = Curl_timeleft(data, NULL, TRUE);
|
||||
if(time_left < 0) {
|
||||
failf(data, "timed out before SSL handshake");
|
||||
result = CURLE_OPERATION_TIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Force the handshake now */
|
||||
timeout = PR_MillisecondsToInterval((PRUint32) time_left);
|
||||
if(SSL_ForceHandshakeWithTimeout(backend->handle, timeout) != SECSuccess) {
|
||||
if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
|
||||
/* blocking direction is updated by nss_update_connecting_state() */
|
||||
return CURLE_AGAIN;
|
||||
else if(SSL_SET_OPTION(certverifyresult) == SSL_ERROR_BAD_CERT_DOMAIN)
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
else if(SSL_SET_OPTION(certverifyresult) != 0)
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
goto error;
|
||||
}
|
||||
|
||||
result = display_conn_info(data, backend->handle);
|
||||
if(result)
|
||||
goto error;
|
||||
|
||||
if(SSL_CONN_CONFIG(issuercert)) {
|
||||
SECStatus ret = SECFailure;
|
||||
char *nickname = dup_nickname(data, SSL_CONN_CONFIG(issuercert));
|
||||
if(nickname) {
|
||||
/* we support only nicknames in case of issuercert for now */
|
||||
ret = check_issuer_cert(backend->handle, nickname);
|
||||
free(nickname);
|
||||
}
|
||||
|
||||
if(SECFailure == ret) {
|
||||
infof(data, "SSL certificate issuer check failed");
|
||||
result = CURLE_SSL_ISSUER_ERROR;
|
||||
goto error;
|
||||
}
|
||||
else {
|
||||
infof(data, "SSL certificate issuer check ok");
|
||||
}
|
||||
}
|
||||
|
||||
result = cmp_peer_pubkey(connssl, SSL_PINNED_PUB_KEY());
|
||||
if(result)
|
||||
/* status already printed */
|
||||
goto error;
|
||||
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
return nss_fail_connect(connssl, data, result);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* Provenance: curl/curl nss_do_connect (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: nss_do_connect
|
||||
* relpath: lib/vtls/nss.c
|
||||
*/
|
||||
|
||||
static CURLcode nss_do_connect(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
CURLcode result = CURLE_SSL_CONNECT_ERROR;
|
||||
PRUint32 timeout;
|
||||
|
||||
/* check timeout situation */
|
||||
const timediff_t time_left = Curl_timeleft(data, NULL, TRUE);
|
||||
if(time_left < 0) {
|
||||
failf(data, "timed out before SSL handshake");
|
||||
result = CURLE_OPERATION_TIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Force the handshake now */
|
||||
timeout = PR_MillisecondsToInterval((PRUint32) time_left);
|
||||
if(SSL_ForceHandshakeWithTimeout(backend->handle, timeout) != SECSuccess) {
|
||||
if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
|
||||
/* blocking direction is updated by nss_update_connecting_state() */
|
||||
return CURLE_AGAIN;
|
||||
else if(SSL_SET_OPTION(certverifyresult) == SSL_ERROR_BAD_CERT_DOMAIN)
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
else if(SSL_SET_OPTION(certverifyresult) != 0)
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
goto error;
|
||||
}
|
||||
|
||||
result = display_conn_info(data, backend->handle);
|
||||
if(result)
|
||||
goto error;
|
||||
|
||||
if(SSL_SET_OPTION(issuercert)) {
|
||||
SECStatus ret = SECFailure;
|
||||
char *nickname = dup_nickname(data, SSL_SET_OPTION(issuercert));
|
||||
if(nickname) {
|
||||
/* we support only nicknames in case of issuercert for now */
|
||||
ret = check_issuer_cert(backend->handle, nickname);
|
||||
free(nickname);
|
||||
}
|
||||
|
||||
if(SECFailure == ret) {
|
||||
infof(data, "SSL certificate issuer check failed");
|
||||
result = CURLE_SSL_ISSUER_ERROR;
|
||||
goto error;
|
||||
}
|
||||
else {
|
||||
infof(data, "SSL certificate issuer check ok");
|
||||
}
|
||||
}
|
||||
|
||||
result = cmp_peer_pubkey(connssl, SSL_PINNED_PUB_KEY());
|
||||
if(result)
|
||||
/* status already printed */
|
||||
goto error;
|
||||
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
return nss_fail_connect(connssl, data, result);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/* Provenance: curl/curl servercert (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: servercert
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode servercert(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
struct ssl_connect_data *connssl,
|
||||
bool strict)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
int rc;
|
||||
long lerr;
|
||||
X509 *issuer;
|
||||
BIO *fp = NULL;
|
||||
char error_buffer[256]="";
|
||||
char buffer[2048];
|
||||
const char *ptr;
|
||||
BIO *mem = BIO_new(BIO_s_mem());
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
|
||||
if(data->set.ssl.certinfo)
|
||||
/* we've been asked to gather certificate info! */
|
||||
(void)get_cert_chain(data, connssl);
|
||||
|
||||
backend->server_cert = SSL_get_peer_certificate(backend->handle);
|
||||
if(!backend->server_cert) {
|
||||
BIO_free(mem);
|
||||
if(!strict)
|
||||
return CURLE_OK;
|
||||
|
||||
failf(data, "SSL: couldn't get peer certificate!");
|
||||
return CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
|
||||
infof(data, "%s certificate:", SSL_IS_PROXY() ? "Proxy" : "Server");
|
||||
|
||||
rc = x509_name_oneline(X509_get_subject_name(backend->server_cert),
|
||||
buffer, sizeof(buffer));
|
||||
infof(data, " subject: %s", rc?"[NONE]":buffer);
|
||||
|
||||
#ifndef CURL_DISABLE_VERBOSE_STRINGS
|
||||
{
|
||||
long len;
|
||||
ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert));
|
||||
len = BIO_get_mem_data(mem, (char **) &ptr);
|
||||
infof(data, " start date: %.*s", (int)len, ptr);
|
||||
(void)BIO_reset(mem);
|
||||
|
||||
ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert));
|
||||
len = BIO_get_mem_data(mem, (char **) &ptr);
|
||||
infof(data, " expire date: %.*s", (int)len, ptr);
|
||||
(void)BIO_reset(mem);
|
||||
}
|
||||
#endif
|
||||
|
||||
BIO_free(mem);
|
||||
|
||||
if(SSL_CONN_CONFIG(verifyhost)) {
|
||||
result = verifyhost(data, conn, backend->server_cert);
|
||||
if(result) {
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert),
|
||||
buffer, sizeof(buffer));
|
||||
if(rc) {
|
||||
if(strict)
|
||||
failf(data, "SSL: couldn't get X509-issuer name!");
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
else {
|
||||
infof(data, " issuer: %s", buffer);
|
||||
|
||||
/* We could do all sorts of certificate verification stuff here before
|
||||
deallocating the certificate. */
|
||||
|
||||
/* e.g. match issuer name with provided issuer certificate */
|
||||
if(SSL_CONN_CONFIG(issuercert) || SSL_CONN_CONFIG(issuercert_blob)) {
|
||||
if(SSL_CONN_CONFIG(issuercert_blob))
|
||||
fp = BIO_new_mem_buf(SSL_CONN_CONFIG(issuercert_blob)->data,
|
||||
(int)SSL_CONN_CONFIG(issuercert_blob)->len);
|
||||
else {
|
||||
fp = BIO_new(BIO_s_file());
|
||||
if(!fp) {
|
||||
failf(data,
|
||||
"BIO_new return NULL, " OSSL_PACKAGE
|
||||
" error %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)) );
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if(BIO_read_filename(fp, SSL_CONN_CONFIG(issuercert)) <= 0) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Unable to open issuer cert (%s)",
|
||||
SSL_CONN_CONFIG(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL);
|
||||
if(!issuer) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Unable to read issuer cert (%s)",
|
||||
SSL_CONN_CONFIG(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
|
||||
if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Certificate issuer check failed (%s)",
|
||||
SSL_CONN_CONFIG(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
|
||||
infof(data, " SSL certificate issuer check ok (%s)",
|
||||
SSL_CONN_CONFIG(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
}
|
||||
|
||||
lerr = SSL_get_verify_result(backend->handle);
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = lerr;
|
||||
if(lerr != X509_V_OK) {
|
||||
if(SSL_CONN_CONFIG(verifypeer)) {
|
||||
/* We probably never reach this, because SSL_connect() will fail
|
||||
and we return earlier if verifypeer is set? */
|
||||
if(strict)
|
||||
failf(data, "SSL certificate verify result: %s (%ld)",
|
||||
X509_verify_cert_error_string(lerr), lerr);
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
else
|
||||
infof(data, " SSL certificate verify result: %s (%ld),"
|
||||
" continuing anyway.",
|
||||
X509_verify_cert_error_string(lerr), lerr);
|
||||
}
|
||||
else
|
||||
infof(data, " SSL certificate verify ok.");
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus)) {
|
||||
result = verifystatus(data, connssl);
|
||||
if(result) {
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!strict)
|
||||
/* when not strict, we don't bother about the verify cert problems */
|
||||
result = CURLE_OK;
|
||||
|
||||
ptr = SSL_PINNED_PUB_KEY();
|
||||
if(!result && ptr) {
|
||||
result = pkp_pin_peer_pubkey(data, backend->server_cert, ptr);
|
||||
if(result)
|
||||
failf(data, "SSL: public key does not match pinned public key!");
|
||||
}
|
||||
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
connssl->connecting_state = ssl_connect_done;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/* Provenance: curl/curl servercert (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: servercert
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode servercert(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
struct ssl_connect_data *connssl,
|
||||
bool strict)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
int rc;
|
||||
long lerr;
|
||||
X509 *issuer;
|
||||
BIO *fp = NULL;
|
||||
char error_buffer[256]="";
|
||||
char buffer[2048];
|
||||
const char *ptr;
|
||||
BIO *mem = BIO_new(BIO_s_mem());
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
|
||||
if(data->set.ssl.certinfo)
|
||||
/* we've been asked to gather certificate info! */
|
||||
(void)get_cert_chain(data, connssl);
|
||||
|
||||
backend->server_cert = SSL_get_peer_certificate(backend->handle);
|
||||
if(!backend->server_cert) {
|
||||
BIO_free(mem);
|
||||
if(!strict)
|
||||
return CURLE_OK;
|
||||
|
||||
failf(data, "SSL: couldn't get peer certificate!");
|
||||
return CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
|
||||
infof(data, "%s certificate:", SSL_IS_PROXY() ? "Proxy" : "Server");
|
||||
|
||||
rc = x509_name_oneline(X509_get_subject_name(backend->server_cert),
|
||||
buffer, sizeof(buffer));
|
||||
infof(data, " subject: %s", rc?"[NONE]":buffer);
|
||||
|
||||
#ifndef CURL_DISABLE_VERBOSE_STRINGS
|
||||
{
|
||||
long len;
|
||||
ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert));
|
||||
len = BIO_get_mem_data(mem, (char **) &ptr);
|
||||
infof(data, " start date: %.*s", (int)len, ptr);
|
||||
(void)BIO_reset(mem);
|
||||
|
||||
ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert));
|
||||
len = BIO_get_mem_data(mem, (char **) &ptr);
|
||||
infof(data, " expire date: %.*s", (int)len, ptr);
|
||||
(void)BIO_reset(mem);
|
||||
}
|
||||
#endif
|
||||
|
||||
BIO_free(mem);
|
||||
|
||||
if(SSL_CONN_CONFIG(verifyhost)) {
|
||||
result = verifyhost(data, conn, backend->server_cert);
|
||||
if(result) {
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert),
|
||||
buffer, sizeof(buffer));
|
||||
if(rc) {
|
||||
if(strict)
|
||||
failf(data, "SSL: couldn't get X509-issuer name!");
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
else {
|
||||
infof(data, " issuer: %s", buffer);
|
||||
|
||||
/* We could do all sorts of certificate verification stuff here before
|
||||
deallocating the certificate. */
|
||||
|
||||
/* e.g. match issuer name with provided issuer certificate */
|
||||
if(SSL_SET_OPTION(issuercert) || SSL_SET_OPTION(issuercert_blob)) {
|
||||
if(SSL_SET_OPTION(issuercert_blob))
|
||||
fp = BIO_new_mem_buf(SSL_SET_OPTION(issuercert_blob)->data,
|
||||
(int)SSL_SET_OPTION(issuercert_blob)->len);
|
||||
else {
|
||||
fp = BIO_new(BIO_s_file());
|
||||
if(!fp) {
|
||||
failf(data,
|
||||
"BIO_new return NULL, " OSSL_PACKAGE
|
||||
" error %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)) );
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if(BIO_read_filename(fp, SSL_SET_OPTION(issuercert)) <= 0) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Unable to open issuer cert (%s)",
|
||||
SSL_SET_OPTION(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL);
|
||||
if(!issuer) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Unable to read issuer cert (%s)",
|
||||
SSL_SET_OPTION(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
|
||||
if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) {
|
||||
if(strict)
|
||||
failf(data, "SSL: Certificate issuer check failed (%s)",
|
||||
SSL_SET_OPTION(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return CURLE_SSL_ISSUER_ERROR;
|
||||
}
|
||||
|
||||
infof(data, " SSL certificate issuer check ok (%s)",
|
||||
SSL_SET_OPTION(issuercert));
|
||||
BIO_free(fp);
|
||||
X509_free(issuer);
|
||||
}
|
||||
|
||||
lerr = SSL_get_verify_result(backend->handle);
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = lerr;
|
||||
if(lerr != X509_V_OK) {
|
||||
if(SSL_CONN_CONFIG(verifypeer)) {
|
||||
/* We probably never reach this, because SSL_connect() will fail
|
||||
and we return earlier if verifypeer is set? */
|
||||
if(strict)
|
||||
failf(data, "SSL certificate verify result: %s (%ld)",
|
||||
X509_verify_cert_error_string(lerr), lerr);
|
||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||
}
|
||||
else
|
||||
infof(data, " SSL certificate verify result: %s (%ld),"
|
||||
" continuing anyway.",
|
||||
X509_verify_cert_error_string(lerr), lerr);
|
||||
}
|
||||
else
|
||||
infof(data, " SSL certificate verify ok.");
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus)) {
|
||||
result = verifystatus(data, connssl);
|
||||
if(result) {
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!strict)
|
||||
/* when not strict, we don't bother about the verify cert problems */
|
||||
result = CURLE_OK;
|
||||
|
||||
ptr = SSL_PINNED_PUB_KEY();
|
||||
if(!result && ptr) {
|
||||
result = pkp_pin_peer_pubkey(data, backend->server_cert, ptr);
|
||||
if(result)
|
||||
failf(data, "SSL: public key does not match pinned public key!");
|
||||
}
|
||||
|
||||
X509_free(backend->server_cert);
|
||||
backend->server_cert = NULL;
|
||||
connssl->connecting_state = ssl_connect_done;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
/* Provenance: curl/curl create_conn (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: create_conn
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
* create_conn() sets up a new connectdata struct, or re-uses an already
|
||||
* existing one, and resolves host name.
|
||||
*
|
||||
* if this function returns CURLE_OK and *async is set to TRUE, the resolve
|
||||
* response will be coming asynchronously. If *async is FALSE, the name is
|
||||
* already resolved.
|
||||
*
|
||||
* @param data The sessionhandle pointer
|
||||
* @param in_connect is set to the next connection data pointer
|
||||
* @param async is set TRUE when an async DNS resolution is pending
|
||||
* @see Curl_setup_conn()
|
||||
*
|
||||
*/
|
||||
|
||||
static CURLcode create_conn(struct Curl_easy *data,
|
||||
struct connectdata **in_connect,
|
||||
bool *async)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn;
|
||||
struct connectdata *conn_temp = NULL;
|
||||
bool reuse;
|
||||
bool connections_available = TRUE;
|
||||
bool force_reuse = FALSE;
|
||||
bool waitpipe = FALSE;
|
||||
size_t max_host_connections = Curl_multi_max_host_connections(data->multi);
|
||||
size_t max_total_connections = Curl_multi_max_total_connections(data->multi);
|
||||
|
||||
*async = FALSE;
|
||||
*in_connect = NULL;
|
||||
|
||||
/*************************************************************
|
||||
* Check input data
|
||||
*************************************************************/
|
||||
if(!data->state.url) {
|
||||
result = CURLE_URL_MALFORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* First, split up the current URL in parts so that we can use the
|
||||
parts for checking against the already present connections. In order
|
||||
to not have to modify everything at once, we allocate a temporary
|
||||
connection data struct and fill in for comparison purposes. */
|
||||
conn = allocate_conn(data);
|
||||
|
||||
if(!conn) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* We must set the return variable as soon as possible, so that our
|
||||
parent can cleanup any possible allocs we may have done before
|
||||
any failure */
|
||||
*in_connect = conn;
|
||||
|
||||
result = parseurlandfillconn(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
if(data->set.str[STRING_SASL_AUTHZID]) {
|
||||
conn->sasl_authzid = strdup(data->set.str[STRING_SASL_AUTHZID]);
|
||||
if(!conn->sasl_authzid) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_UNIX_SOCKETS
|
||||
if(data->set.str[STRING_UNIX_SOCKET_PATH]) {
|
||||
conn->unix_domain_socket = strdup(data->set.str[STRING_UNIX_SOCKET_PATH]);
|
||||
if(!conn->unix_domain_socket) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
conn->bits.abstract_unix_socket = data->set.abstract_unix_socket;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* After the unix socket init but before the proxy vars are used, parse and
|
||||
initialize the proxy vars */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
result = create_conn_helper_init_proxy(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* If the protocol is using SSL and HTTP proxy is used, we set
|
||||
* the tunnel_proxy bit.
|
||||
*************************************************************/
|
||||
if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy)
|
||||
conn->bits.tunnel_proxy = TRUE;
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Figure out the remote port number and fix it in the URL
|
||||
*************************************************************/
|
||||
result = parse_remote_port(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/* Check for overridden login details and set them accordingly so that
|
||||
they are known when protocol->setup_connection is called! */
|
||||
result = override_login(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
result = set_login(conn); /* default credentials */
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* Process the "connect to" linked list of hostname/port mappings.
|
||||
* Do this after the remote port number has been fixed in the URL.
|
||||
*************************************************************/
|
||||
result = parse_connect_to_slist(data, conn, data->set.connect_to);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* IDN-convert the hostnames
|
||||
*************************************************************/
|
||||
result = Curl_idnconvert_hostname(data, &conn->host);
|
||||
if(result)
|
||||
goto out;
|
||||
if(conn->bits.conn_to_host) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->conn_to_host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->http_proxy.host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
if(conn->bits.socksproxy) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->socks_proxy.host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Check whether the host and the "connect to host" are equal.
|
||||
* Do this after the hostnames have been IDN-converted.
|
||||
*************************************************************/
|
||||
if(conn->bits.conn_to_host &&
|
||||
strcasecompare(conn->conn_to_host.name, conn->host.name)) {
|
||||
conn->bits.conn_to_host = FALSE;
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Check whether the port and the "connect to port" are equal.
|
||||
* Do this after the remote port number has been fixed in the URL.
|
||||
*************************************************************/
|
||||
if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
|
||||
conn->bits.conn_to_port = FALSE;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
/*************************************************************
|
||||
* If the "connect to" feature is used with an HTTP proxy,
|
||||
* we set the tunnel_proxy bit.
|
||||
*************************************************************/
|
||||
if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
|
||||
conn->bits.httpproxy)
|
||||
conn->bits.tunnel_proxy = TRUE;
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Setup internals depending on protocol. Needs to be done after
|
||||
* we figured out what/if proxy to use.
|
||||
*************************************************************/
|
||||
result = setup_connection_internals(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
conn->recv[FIRSTSOCKET] = Curl_recv_plain;
|
||||
conn->send[FIRSTSOCKET] = Curl_send_plain;
|
||||
conn->recv[SECONDARYSOCKET] = Curl_recv_plain;
|
||||
conn->send[SECONDARYSOCKET] = Curl_send_plain;
|
||||
|
||||
conn->bits.tcp_fastopen = data->set.tcp_fastopen;
|
||||
|
||||
/***********************************************************************
|
||||
* file: is a special case in that it doesn't need a network connection
|
||||
***********************************************************************/
|
||||
#ifndef CURL_DISABLE_FILE
|
||||
if(conn->handler->flags & PROTOPT_NONETWORK) {
|
||||
bool done;
|
||||
/* this is supposed to be the connect function so we better at least check
|
||||
that the file is present here! */
|
||||
DEBUGASSERT(conn->handler->connect_it);
|
||||
Curl_persistconninfo(data, conn, NULL, -1);
|
||||
result = conn->handler->connect_it(data, &done);
|
||||
|
||||
/* Setup a "faked" transfer that'll do nothing */
|
||||
if(!result) {
|
||||
conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; /* we are "connected */
|
||||
|
||||
Curl_attach_connnection(data, conn);
|
||||
result = Curl_conncache_add_conn(data);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Setup whatever necessary for a resumed transfer
|
||||
*/
|
||||
result = setup_range(data);
|
||||
if(result) {
|
||||
DEBUGASSERT(conn->handler->done);
|
||||
/* we ignore the return code for the protocol-specific DONE */
|
||||
(void)conn->handler->done(data, result, FALSE);
|
||||
goto out;
|
||||
}
|
||||
Curl_setup_transfer(data, -1, -1, FALSE, -1);
|
||||
}
|
||||
|
||||
/* since we skip do_init() */
|
||||
Curl_init_do(data, conn);
|
||||
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Get a cloned copy of the SSL config situation stored in the
|
||||
connection struct. But to get this going nicely, we must first make
|
||||
sure that the strings in the master copy are pointing to the correct
|
||||
strings in the session handle strings array!
|
||||
|
||||
Keep in mind that the pointers in the master copy are pointing to strings
|
||||
that will be freed as part of the Curl_easy struct, but all cloned
|
||||
copies will be separately allocated.
|
||||
*/
|
||||
data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH];
|
||||
data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE];
|
||||
data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT];
|
||||
data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT];
|
||||
data->set.ssl.primary.random_file = data->set.str[STRING_SSL_RANDOM_FILE];
|
||||
data->set.ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
|
||||
data->set.ssl.primary.cipher_list =
|
||||
data->set.str[STRING_SSL_CIPHER_LIST];
|
||||
data->set.ssl.primary.cipher_list13 =
|
||||
data->set.str[STRING_SSL_CIPHER13_LIST];
|
||||
data->set.ssl.primary.pinned_key =
|
||||
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
|
||||
data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT];
|
||||
data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO];
|
||||
data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES];
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY];
|
||||
data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY];
|
||||
data->set.proxy_ssl.primary.random_file =
|
||||
data->set.str[STRING_SSL_RANDOM_FILE];
|
||||
data->set.proxy_ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
|
||||
data->set.proxy_ssl.primary.cipher_list =
|
||||
data->set.str[STRING_SSL_CIPHER_LIST_PROXY];
|
||||
data->set.proxy_ssl.primary.cipher_list13 =
|
||||
data->set.str[STRING_SSL_CIPHER13_LIST_PROXY];
|
||||
data->set.proxy_ssl.primary.pinned_key =
|
||||
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY];
|
||||
data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY];
|
||||
data->set.proxy_ssl.primary.ca_info_blob =
|
||||
data->set.blobs[BLOB_CAINFO_PROXY];
|
||||
data->set.proxy_ssl.primary.issuercert =
|
||||
data->set.str[STRING_SSL_ISSUERCERT_PROXY];
|
||||
data->set.proxy_ssl.primary.issuercert_blob =
|
||||
data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY];
|
||||
data->set.proxy_ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY];
|
||||
data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY];
|
||||
data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY];
|
||||
data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY];
|
||||
data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY];
|
||||
data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY];
|
||||
data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY];
|
||||
#endif
|
||||
data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE];
|
||||
data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE];
|
||||
data->set.ssl.key = data->set.str[STRING_KEY];
|
||||
data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE];
|
||||
data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD];
|
||||
data->set.ssl.primary.clientcert = data->set.str[STRING_CERT];
|
||||
#ifdef USE_TLS_SRP
|
||||
data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME];
|
||||
data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD];
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
data->set.proxy_ssl.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY];
|
||||
data->set.proxy_ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY];
|
||||
#endif
|
||||
#endif
|
||||
data->set.ssl.key_blob = data->set.blobs[BLOB_KEY];
|
||||
|
||||
if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary,
|
||||
&conn->ssl_config)) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(!Curl_clone_primary_ssl_config(&data->set.proxy_ssl.primary,
|
||||
&conn->proxy_ssl_config)) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
prune_dead_connections(data);
|
||||
|
||||
/*************************************************************
|
||||
* Check the current list of connections to see if we can
|
||||
* re-use an already existing one or if we have to create a
|
||||
* new one.
|
||||
*************************************************************/
|
||||
|
||||
DEBUGASSERT(conn->user);
|
||||
DEBUGASSERT(conn->passwd);
|
||||
|
||||
/* reuse_fresh is TRUE if we are told to use a new connection by force, but
|
||||
we only acknowledge this option if this is not a re-used connection
|
||||
already (which happens due to follow-location or during a HTTP
|
||||
authentication phase). CONNECT_ONLY transfers also refuse reuse. */
|
||||
if((data->set.reuse_fresh && !data->state.this_is_a_follow) ||
|
||||
data->set.connect_only)
|
||||
reuse = FALSE;
|
||||
else
|
||||
reuse = ConnectionExists(data, conn, &conn_temp, &force_reuse, &waitpipe);
|
||||
|
||||
if(reuse) {
|
||||
/*
|
||||
* We already have a connection for this, we got the former connection in
|
||||
* the conn_temp variable and thus we need to cleanup the one we just
|
||||
* allocated before we can move along and use the previously existing one.
|
||||
*/
|
||||
reuse_conn(data, conn, conn_temp);
|
||||
#ifdef USE_SSL
|
||||
free(conn->ssl_extra);
|
||||
#endif
|
||||
free(conn); /* we don't need this anymore */
|
||||
conn = conn_temp;
|
||||
*in_connect = conn;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
infof(data, "Re-using existing connection! (#%ld) with %s %s",
|
||||
conn->connection_id,
|
||||
conn->bits.proxy?"proxy":"host",
|
||||
conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
|
||||
conn->http_proxy.host.name ? conn->http_proxy.host.dispname :
|
||||
conn->host.dispname);
|
||||
#else
|
||||
infof(data, "Re-using existing connection! (#%ld) with host %s",
|
||||
conn->connection_id, conn->host.dispname);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* We have decided that we want a new connection. However, we may not
|
||||
be able to do that if we have reached the limit of how many
|
||||
connections we are allowed to open. */
|
||||
|
||||
if(conn->handler->flags & PROTOPT_ALPN_NPN) {
|
||||
/* The protocol wants it, so set the bits if enabled in the easy handle
|
||||
(default) */
|
||||
if(data->set.ssl_enable_alpn)
|
||||
conn->bits.tls_enable_alpn = TRUE;
|
||||
if(data->set.ssl_enable_npn)
|
||||
conn->bits.tls_enable_npn = TRUE;
|
||||
}
|
||||
|
||||
if(waitpipe)
|
||||
/* There is a connection that *might* become usable for multiplexing
|
||||
"soon", and we wait for that */
|
||||
connections_available = FALSE;
|
||||
else {
|
||||
/* this gets a lock on the conncache */
|
||||
const char *bundlehost;
|
||||
struct connectbundle *bundle =
|
||||
Curl_conncache_find_bundle(data, conn, data->state.conn_cache,
|
||||
&bundlehost);
|
||||
|
||||
if(max_host_connections > 0 && bundle &&
|
||||
(bundle->num_connections >= max_host_connections)) {
|
||||
struct connectdata *conn_candidate;
|
||||
|
||||
/* The bundle is full. Extract the oldest connection. */
|
||||
conn_candidate = Curl_conncache_extract_bundle(data, bundle);
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
if(conn_candidate)
|
||||
(void)Curl_disconnect(data, conn_candidate, FALSE);
|
||||
else {
|
||||
infof(data, "No more connections allowed to host %s: %zu",
|
||||
bundlehost, max_host_connections);
|
||||
connections_available = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
}
|
||||
|
||||
if(connections_available &&
|
||||
(max_total_connections > 0) &&
|
||||
(Curl_conncache_size(data) >= max_total_connections)) {
|
||||
struct connectdata *conn_candidate;
|
||||
|
||||
/* The cache is full. Let's see if we can kill a connection. */
|
||||
conn_candidate = Curl_conncache_extract_oldest(data);
|
||||
if(conn_candidate)
|
||||
(void)Curl_disconnect(data, conn_candidate, FALSE);
|
||||
else {
|
||||
infof(data, "No connections available in cache");
|
||||
connections_available = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if(!connections_available) {
|
||||
infof(data, "No connections available.");
|
||||
|
||||
conn_free(conn);
|
||||
*in_connect = NULL;
|
||||
|
||||
result = CURLE_NO_CONNECTION_AVAILABLE;
|
||||
goto out;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* This is a brand new connection, so let's store it in the connection
|
||||
* cache of ours!
|
||||
*/
|
||||
Curl_attach_connnection(data, conn);
|
||||
result = Curl_conncache_add_conn(data);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
|
||||
#if defined(USE_NTLM)
|
||||
/* If NTLM is requested in a part of this connection, make sure we don't
|
||||
assume the state is fine as this is a fresh connection and NTLM is
|
||||
connection based. */
|
||||
if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) &&
|
||||
data->state.authhost.done) {
|
||||
infof(data, "NTLM picked AND auth done set, clear picked!");
|
||||
data->state.authhost.picked = CURLAUTH_NONE;
|
||||
data->state.authhost.done = FALSE;
|
||||
}
|
||||
|
||||
if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) &&
|
||||
data->state.authproxy.done) {
|
||||
infof(data, "NTLM-proxy picked AND auth done set, clear picked!");
|
||||
data->state.authproxy.picked = CURLAUTH_NONE;
|
||||
data->state.authproxy.done = FALSE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Setup and init stuff before DO starts, in preparing for the transfer. */
|
||||
Curl_init_do(data, conn);
|
||||
|
||||
/*
|
||||
* Setup whatever necessary for a resumed transfer
|
||||
*/
|
||||
result = setup_range(data);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/* Continue connectdata initialization here. */
|
||||
|
||||
/*
|
||||
* Inherit the proper values from the urldata struct AFTER we have arranged
|
||||
* the persistent connection stuff
|
||||
*/
|
||||
conn->seek_func = data->set.seek_func;
|
||||
conn->seek_client = data->set.seek_client;
|
||||
|
||||
/*************************************************************
|
||||
* Resolve the address of the server or proxy
|
||||
*************************************************************/
|
||||
result = resolve_server(data, conn, async);
|
||||
|
||||
/* Strip trailing dots. resolve_server copied the name. */
|
||||
strip_trailing_dot(&conn->host);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy)
|
||||
strip_trailing_dot(&conn->http_proxy.host);
|
||||
if(conn->bits.socksproxy)
|
||||
strip_trailing_dot(&conn->socks_proxy.host);
|
||||
#endif
|
||||
if(conn->bits.conn_to_host)
|
||||
strip_trailing_dot(&conn->conn_to_host);
|
||||
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/* Provenance: curl/curl create_conn (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* parent: fd9b40bf8dfd43edcbc0d254d613d95a11061c05
|
||||
* commit_url: https://github.com/curl/curl/commit/5ea3145850ebff1dc2b13d17440300a01ca38161
|
||||
* cve: CVE-2021-22924
|
||||
* license: curl
|
||||
* function: create_conn
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
* create_conn() sets up a new connectdata struct, or re-uses an already
|
||||
* existing one, and resolves host name.
|
||||
*
|
||||
* if this function returns CURLE_OK and *async is set to TRUE, the resolve
|
||||
* response will be coming asynchronously. If *async is FALSE, the name is
|
||||
* already resolved.
|
||||
*
|
||||
* @param data The sessionhandle pointer
|
||||
* @param in_connect is set to the next connection data pointer
|
||||
* @param async is set TRUE when an async DNS resolution is pending
|
||||
* @see Curl_setup_conn()
|
||||
*
|
||||
*/
|
||||
|
||||
static CURLcode create_conn(struct Curl_easy *data,
|
||||
struct connectdata **in_connect,
|
||||
bool *async)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn;
|
||||
struct connectdata *conn_temp = NULL;
|
||||
bool reuse;
|
||||
bool connections_available = TRUE;
|
||||
bool force_reuse = FALSE;
|
||||
bool waitpipe = FALSE;
|
||||
size_t max_host_connections = Curl_multi_max_host_connections(data->multi);
|
||||
size_t max_total_connections = Curl_multi_max_total_connections(data->multi);
|
||||
|
||||
*async = FALSE;
|
||||
*in_connect = NULL;
|
||||
|
||||
/*************************************************************
|
||||
* Check input data
|
||||
*************************************************************/
|
||||
if(!data->state.url) {
|
||||
result = CURLE_URL_MALFORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* First, split up the current URL in parts so that we can use the
|
||||
parts for checking against the already present connections. In order
|
||||
to not have to modify everything at once, we allocate a temporary
|
||||
connection data struct and fill in for comparison purposes. */
|
||||
conn = allocate_conn(data);
|
||||
|
||||
if(!conn) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* We must set the return variable as soon as possible, so that our
|
||||
parent can cleanup any possible allocs we may have done before
|
||||
any failure */
|
||||
*in_connect = conn;
|
||||
|
||||
result = parseurlandfillconn(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
if(data->set.str[STRING_SASL_AUTHZID]) {
|
||||
conn->sasl_authzid = strdup(data->set.str[STRING_SASL_AUTHZID]);
|
||||
if(!conn->sasl_authzid) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_UNIX_SOCKETS
|
||||
if(data->set.str[STRING_UNIX_SOCKET_PATH]) {
|
||||
conn->unix_domain_socket = strdup(data->set.str[STRING_UNIX_SOCKET_PATH]);
|
||||
if(!conn->unix_domain_socket) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
conn->bits.abstract_unix_socket = data->set.abstract_unix_socket;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* After the unix socket init but before the proxy vars are used, parse and
|
||||
initialize the proxy vars */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
result = create_conn_helper_init_proxy(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* If the protocol is using SSL and HTTP proxy is used, we set
|
||||
* the tunnel_proxy bit.
|
||||
*************************************************************/
|
||||
if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy)
|
||||
conn->bits.tunnel_proxy = TRUE;
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Figure out the remote port number and fix it in the URL
|
||||
*************************************************************/
|
||||
result = parse_remote_port(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/* Check for overridden login details and set them accordingly so that
|
||||
they are known when protocol->setup_connection is called! */
|
||||
result = override_login(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
result = set_login(conn); /* default credentials */
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* Process the "connect to" linked list of hostname/port mappings.
|
||||
* Do this after the remote port number has been fixed in the URL.
|
||||
*************************************************************/
|
||||
result = parse_connect_to_slist(data, conn, data->set.connect_to);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*************************************************************
|
||||
* IDN-convert the hostnames
|
||||
*************************************************************/
|
||||
result = Curl_idnconvert_hostname(data, &conn->host);
|
||||
if(result)
|
||||
goto out;
|
||||
if(conn->bits.conn_to_host) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->conn_to_host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->http_proxy.host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
if(conn->bits.socksproxy) {
|
||||
result = Curl_idnconvert_hostname(data, &conn->socks_proxy.host);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Check whether the host and the "connect to host" are equal.
|
||||
* Do this after the hostnames have been IDN-converted.
|
||||
*************************************************************/
|
||||
if(conn->bits.conn_to_host &&
|
||||
strcasecompare(conn->conn_to_host.name, conn->host.name)) {
|
||||
conn->bits.conn_to_host = FALSE;
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Check whether the port and the "connect to port" are equal.
|
||||
* Do this after the remote port number has been fixed in the URL.
|
||||
*************************************************************/
|
||||
if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
|
||||
conn->bits.conn_to_port = FALSE;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
/*************************************************************
|
||||
* If the "connect to" feature is used with an HTTP proxy,
|
||||
* we set the tunnel_proxy bit.
|
||||
*************************************************************/
|
||||
if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
|
||||
conn->bits.httpproxy)
|
||||
conn->bits.tunnel_proxy = TRUE;
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
* Setup internals depending on protocol. Needs to be done after
|
||||
* we figured out what/if proxy to use.
|
||||
*************************************************************/
|
||||
result = setup_connection_internals(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
conn->recv[FIRSTSOCKET] = Curl_recv_plain;
|
||||
conn->send[FIRSTSOCKET] = Curl_send_plain;
|
||||
conn->recv[SECONDARYSOCKET] = Curl_recv_plain;
|
||||
conn->send[SECONDARYSOCKET] = Curl_send_plain;
|
||||
|
||||
conn->bits.tcp_fastopen = data->set.tcp_fastopen;
|
||||
|
||||
/***********************************************************************
|
||||
* file: is a special case in that it doesn't need a network connection
|
||||
***********************************************************************/
|
||||
#ifndef CURL_DISABLE_FILE
|
||||
if(conn->handler->flags & PROTOPT_NONETWORK) {
|
||||
bool done;
|
||||
/* this is supposed to be the connect function so we better at least check
|
||||
that the file is present here! */
|
||||
DEBUGASSERT(conn->handler->connect_it);
|
||||
Curl_persistconninfo(data, conn, NULL, -1);
|
||||
result = conn->handler->connect_it(data, &done);
|
||||
|
||||
/* Setup a "faked" transfer that'll do nothing */
|
||||
if(!result) {
|
||||
conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; /* we are "connected */
|
||||
|
||||
Curl_attach_connnection(data, conn);
|
||||
result = Curl_conncache_add_conn(data);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Setup whatever necessary for a resumed transfer
|
||||
*/
|
||||
result = setup_range(data);
|
||||
if(result) {
|
||||
DEBUGASSERT(conn->handler->done);
|
||||
/* we ignore the return code for the protocol-specific DONE */
|
||||
(void)conn->handler->done(data, result, FALSE);
|
||||
goto out;
|
||||
}
|
||||
Curl_setup_transfer(data, -1, -1, FALSE, -1);
|
||||
}
|
||||
|
||||
/* since we skip do_init() */
|
||||
Curl_init_do(data, conn);
|
||||
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Get a cloned copy of the SSL config situation stored in the
|
||||
connection struct. But to get this going nicely, we must first make
|
||||
sure that the strings in the master copy are pointing to the correct
|
||||
strings in the session handle strings array!
|
||||
|
||||
Keep in mind that the pointers in the master copy are pointing to strings
|
||||
that will be freed as part of the Curl_easy struct, but all cloned
|
||||
copies will be separately allocated.
|
||||
*/
|
||||
data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH];
|
||||
data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE];
|
||||
data->set.ssl.primary.random_file = data->set.str[STRING_SSL_RANDOM_FILE];
|
||||
data->set.ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
|
||||
data->set.ssl.primary.cipher_list =
|
||||
data->set.str[STRING_SSL_CIPHER_LIST];
|
||||
data->set.ssl.primary.cipher_list13 =
|
||||
data->set.str[STRING_SSL_CIPHER13_LIST];
|
||||
data->set.ssl.primary.pinned_key =
|
||||
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
|
||||
data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT];
|
||||
data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO];
|
||||
data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES];
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY];
|
||||
data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY];
|
||||
data->set.proxy_ssl.primary.random_file =
|
||||
data->set.str[STRING_SSL_RANDOM_FILE];
|
||||
data->set.proxy_ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
|
||||
data->set.proxy_ssl.primary.cipher_list =
|
||||
data->set.str[STRING_SSL_CIPHER_LIST_PROXY];
|
||||
data->set.proxy_ssl.primary.cipher_list13 =
|
||||
data->set.str[STRING_SSL_CIPHER13_LIST_PROXY];
|
||||
data->set.proxy_ssl.primary.pinned_key =
|
||||
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY];
|
||||
data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY];
|
||||
data->set.proxy_ssl.primary.ca_info_blob =
|
||||
data->set.blobs[BLOB_CAINFO_PROXY];
|
||||
data->set.proxy_ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY];
|
||||
data->set.proxy_ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY];
|
||||
data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY];
|
||||
data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY];
|
||||
data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY];
|
||||
data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY];
|
||||
data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY];
|
||||
data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY];
|
||||
#endif
|
||||
data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE];
|
||||
data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT];
|
||||
data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE];
|
||||
data->set.ssl.key = data->set.str[STRING_KEY];
|
||||
data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE];
|
||||
data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD];
|
||||
data->set.ssl.primary.clientcert = data->set.str[STRING_CERT];
|
||||
#ifdef USE_TLS_SRP
|
||||
data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME];
|
||||
data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD];
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
data->set.proxy_ssl.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY];
|
||||
data->set.proxy_ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
data->set.ssl.key_blob = data->set.blobs[BLOB_KEY];
|
||||
data->set.ssl.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT];
|
||||
|
||||
if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary,
|
||||
&conn->ssl_config)) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(!Curl_clone_primary_ssl_config(&data->set.proxy_ssl.primary,
|
||||
&conn->proxy_ssl_config)) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
prune_dead_connections(data);
|
||||
|
||||
/*************************************************************
|
||||
* Check the current list of connections to see if we can
|
||||
* re-use an already existing one or if we have to create a
|
||||
* new one.
|
||||
*************************************************************/
|
||||
|
||||
DEBUGASSERT(conn->user);
|
||||
DEBUGASSERT(conn->passwd);
|
||||
|
||||
/* reuse_fresh is TRUE if we are told to use a new connection by force, but
|
||||
we only acknowledge this option if this is not a re-used connection
|
||||
already (which happens due to follow-location or during a HTTP
|
||||
authentication phase). CONNECT_ONLY transfers also refuse reuse. */
|
||||
if((data->set.reuse_fresh && !data->state.this_is_a_follow) ||
|
||||
data->set.connect_only)
|
||||
reuse = FALSE;
|
||||
else
|
||||
reuse = ConnectionExists(data, conn, &conn_temp, &force_reuse, &waitpipe);
|
||||
|
||||
if(reuse) {
|
||||
/*
|
||||
* We already have a connection for this, we got the former connection in
|
||||
* the conn_temp variable and thus we need to cleanup the one we just
|
||||
* allocated before we can move along and use the previously existing one.
|
||||
*/
|
||||
reuse_conn(data, conn, conn_temp);
|
||||
#ifdef USE_SSL
|
||||
free(conn->ssl_extra);
|
||||
#endif
|
||||
free(conn); /* we don't need this anymore */
|
||||
conn = conn_temp;
|
||||
*in_connect = conn;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
infof(data, "Re-using existing connection! (#%ld) with %s %s",
|
||||
conn->connection_id,
|
||||
conn->bits.proxy?"proxy":"host",
|
||||
conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
|
||||
conn->http_proxy.host.name ? conn->http_proxy.host.dispname :
|
||||
conn->host.dispname);
|
||||
#else
|
||||
infof(data, "Re-using existing connection! (#%ld) with host %s",
|
||||
conn->connection_id, conn->host.dispname);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* We have decided that we want a new connection. However, we may not
|
||||
be able to do that if we have reached the limit of how many
|
||||
connections we are allowed to open. */
|
||||
|
||||
if(conn->handler->flags & PROTOPT_ALPN_NPN) {
|
||||
/* The protocol wants it, so set the bits if enabled in the easy handle
|
||||
(default) */
|
||||
if(data->set.ssl_enable_alpn)
|
||||
conn->bits.tls_enable_alpn = TRUE;
|
||||
if(data->set.ssl_enable_npn)
|
||||
conn->bits.tls_enable_npn = TRUE;
|
||||
}
|
||||
|
||||
if(waitpipe)
|
||||
/* There is a connection that *might* become usable for multiplexing
|
||||
"soon", and we wait for that */
|
||||
connections_available = FALSE;
|
||||
else {
|
||||
/* this gets a lock on the conncache */
|
||||
const char *bundlehost;
|
||||
struct connectbundle *bundle =
|
||||
Curl_conncache_find_bundle(data, conn, data->state.conn_cache,
|
||||
&bundlehost);
|
||||
|
||||
if(max_host_connections > 0 && bundle &&
|
||||
(bundle->num_connections >= max_host_connections)) {
|
||||
struct connectdata *conn_candidate;
|
||||
|
||||
/* The bundle is full. Extract the oldest connection. */
|
||||
conn_candidate = Curl_conncache_extract_bundle(data, bundle);
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
if(conn_candidate)
|
||||
(void)Curl_disconnect(data, conn_candidate, FALSE);
|
||||
else {
|
||||
infof(data, "No more connections allowed to host %s: %zu",
|
||||
bundlehost, max_host_connections);
|
||||
connections_available = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
CONNCACHE_UNLOCK(data);
|
||||
|
||||
}
|
||||
|
||||
if(connections_available &&
|
||||
(max_total_connections > 0) &&
|
||||
(Curl_conncache_size(data) >= max_total_connections)) {
|
||||
struct connectdata *conn_candidate;
|
||||
|
||||
/* The cache is full. Let's see if we can kill a connection. */
|
||||
conn_candidate = Curl_conncache_extract_oldest(data);
|
||||
if(conn_candidate)
|
||||
(void)Curl_disconnect(data, conn_candidate, FALSE);
|
||||
else {
|
||||
infof(data, "No connections available in cache");
|
||||
connections_available = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if(!connections_available) {
|
||||
infof(data, "No connections available.");
|
||||
|
||||
conn_free(conn);
|
||||
*in_connect = NULL;
|
||||
|
||||
result = CURLE_NO_CONNECTION_AVAILABLE;
|
||||
goto out;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* This is a brand new connection, so let's store it in the connection
|
||||
* cache of ours!
|
||||
*/
|
||||
Curl_attach_connnection(data, conn);
|
||||
result = Curl_conncache_add_conn(data);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
|
||||
#if defined(USE_NTLM)
|
||||
/* If NTLM is requested in a part of this connection, make sure we don't
|
||||
assume the state is fine as this is a fresh connection and NTLM is
|
||||
connection based. */
|
||||
if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) &&
|
||||
data->state.authhost.done) {
|
||||
infof(data, "NTLM picked AND auth done set, clear picked!");
|
||||
data->state.authhost.picked = CURLAUTH_NONE;
|
||||
data->state.authhost.done = FALSE;
|
||||
}
|
||||
|
||||
if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) &&
|
||||
data->state.authproxy.done) {
|
||||
infof(data, "NTLM-proxy picked AND auth done set, clear picked!");
|
||||
data->state.authproxy.picked = CURLAUTH_NONE;
|
||||
data->state.authproxy.done = FALSE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Setup and init stuff before DO starts, in preparing for the transfer. */
|
||||
Curl_init_do(data, conn);
|
||||
|
||||
/*
|
||||
* Setup whatever necessary for a resumed transfer
|
||||
*/
|
||||
result = setup_range(data);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
/* Continue connectdata initialization here. */
|
||||
|
||||
/*
|
||||
* Inherit the proper values from the urldata struct AFTER we have arranged
|
||||
* the persistent connection stuff
|
||||
*/
|
||||
conn->seek_func = data->set.seek_func;
|
||||
conn->seek_client = data->set.seek_client;
|
||||
|
||||
/*************************************************************
|
||||
* Resolve the address of the server or proxy
|
||||
*************************************************************/
|
||||
result = resolve_server(data, conn, async);
|
||||
|
||||
/* Strip trailing dots. resolve_server copied the name. */
|
||||
strip_trailing_dot(&conn->host);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy)
|
||||
strip_trailing_dot(&conn->http_proxy.host);
|
||||
if(conn->bits.socksproxy)
|
||||
strip_trailing_dot(&conn->socks_proxy.host);
|
||||
#endif
|
||||
if(conn->bits.conn_to_host)
|
||||
strip_trailing_dot(&conn->conn_to_host);
|
||||
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* Provenance: curl/curl suboption (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 894f6ec730597eb243618d33cc84d71add8d6a8a
|
||||
* parent: 12284e008bad5156b67d0ac253331a96d77201d1
|
||||
* commit_url: https://github.com/curl/curl/commit/894f6ec730597eb243618d33cc84d71add8d6a8a
|
||||
* cve: CVE-2021-22925
|
||||
* license: curl
|
||||
* function: suboption
|
||||
* relpath: lib/telnet.c
|
||||
*/
|
||||
|
||||
* suboption()
|
||||
*
|
||||
* Look at the sub-option buffer, and try to be helpful to the other
|
||||
* side.
|
||||
*/
|
||||
|
||||
static void suboption(struct Curl_easy *data)
|
||||
{
|
||||
struct curl_slist *v;
|
||||
unsigned char temp[2048];
|
||||
ssize_t bytes_written;
|
||||
size_t len;
|
||||
int err;
|
||||
char varname[128] = "";
|
||||
char varval[128] = "";
|
||||
struct TELNET *tn = data->req.p.telnet;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2);
|
||||
switch(CURL_SB_GET(tn)) {
|
||||
case CURL_TELOPT_TTYPE:
|
||||
len = strlen(tn->subopt_ttype) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE,
|
||||
CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_XDISPLOC:
|
||||
len = strlen(tn->subopt_xdisploc) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC,
|
||||
CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_NEW_ENVIRON:
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON,
|
||||
CURL_TELQUAL_IS);
|
||||
len = 4;
|
||||
|
||||
for(v = tn->telnet_vars; v; v = v->next) {
|
||||
size_t tmplen = (strlen(v->data) + 1);
|
||||
/* Add the variable only if it fits */
|
||||
if(len + tmplen < (int)sizeof(temp)-6) {
|
||||
int rv;
|
||||
char sep[2] = "";
|
||||
varval[0] = 0;
|
||||
rv = sscanf(v->data, "%127[^,]%1[,]%127s", varname, sep, varval);
|
||||
if(rv == 1)
|
||||
len += msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%s", CURL_NEW_ENV_VAR, varname);
|
||||
else if(rv >= 2)
|
||||
len += msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%s%c%s", CURL_NEW_ENV_VAR, varname,
|
||||
CURL_NEW_ENV_VALUE, varval);
|
||||
}
|
||||
}
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%c", CURL_IAC, CURL_SE);
|
||||
len += 2;
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Provenance: curl/curl suboption (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 12284e008bad5156b67d0ac253331a96d77201d1
|
||||
* parent: 12284e008bad5156b67d0ac253331a96d77201d1
|
||||
* commit_url: https://github.com/curl/curl/commit/894f6ec730597eb243618d33cc84d71add8d6a8a
|
||||
* cve: CVE-2021-22925
|
||||
* license: curl
|
||||
* function: suboption
|
||||
* relpath: lib/telnet.c
|
||||
*/
|
||||
|
||||
* suboption()
|
||||
*
|
||||
* Look at the sub-option buffer, and try to be helpful to the other
|
||||
* side.
|
||||
*/
|
||||
|
||||
static void suboption(struct Curl_easy *data)
|
||||
{
|
||||
struct curl_slist *v;
|
||||
unsigned char temp[2048];
|
||||
ssize_t bytes_written;
|
||||
size_t len;
|
||||
int err;
|
||||
char varname[128] = "";
|
||||
char varval[128] = "";
|
||||
struct TELNET *tn = data->req.p.telnet;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2);
|
||||
switch(CURL_SB_GET(tn)) {
|
||||
case CURL_TELOPT_TTYPE:
|
||||
len = strlen(tn->subopt_ttype) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE,
|
||||
CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_XDISPLOC:
|
||||
len = strlen(tn->subopt_xdisploc) + 4 + 2;
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC,
|
||||
CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE);
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
case CURL_TELOPT_NEW_ENVIRON:
|
||||
msnprintf((char *)temp, sizeof(temp),
|
||||
"%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON,
|
||||
CURL_TELQUAL_IS);
|
||||
len = 4;
|
||||
|
||||
for(v = tn->telnet_vars; v; v = v->next) {
|
||||
size_t tmplen = (strlen(v->data) + 1);
|
||||
/* Add the variable only if it fits */
|
||||
if(len + tmplen < (int)sizeof(temp)-6) {
|
||||
if(sscanf(v->data, "%127[^,],%127s", varname, varval) == 2) {
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%s%c%s", CURL_NEW_ENV_VAR, varname,
|
||||
CURL_NEW_ENV_VALUE, varval);
|
||||
len += tmplen;
|
||||
}
|
||||
}
|
||||
}
|
||||
msnprintf((char *)&temp[len], sizeof(temp) - len,
|
||||
"%c%c", CURL_IAC, CURL_SE);
|
||||
len += 2;
|
||||
bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len);
|
||||
if(bytes_written < 0) {
|
||||
err = SOCKERRNO;
|
||||
failf(data,"Sending data failed (%d)",err);
|
||||
}
|
||||
printsub(data, '>', &temp[2], len-2);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Provenance: curl/curl mqtt_send (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* parent: 409a4412409371db00af0a3fe987e517bfab3d19
|
||||
* commit_url: https://github.com/curl/curl/commit/43157490a5054bd24256fe12876931e8abc9df49
|
||||
* cve: CVE-2021-22945
|
||||
* license: curl
|
||||
* function: mqtt_send
|
||||
* relpath: lib/mqtt.c
|
||||
*/
|
||||
|
||||
static CURLcode mqtt_send(struct Curl_easy *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
|
||||
struct MQTT *mq = data->req.p.mqtt;
|
||||
ssize_t n;
|
||||
result = Curl_write(data, sockfd, buf, len, &n);
|
||||
if(!result)
|
||||
Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
|
||||
if(len != (size_t)n) {
|
||||
size_t nsend = len - n;
|
||||
char *sendleftovers = Curl_memdup(&buf[n], nsend);
|
||||
if(!sendleftovers)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
mq->sendleftovers = sendleftovers;
|
||||
mq->nsend = nsend;
|
||||
}
|
||||
else {
|
||||
mq->sendleftovers = NULL;
|
||||
mq->nsend = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Provenance: curl/curl mqtt_send (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 409a4412409371db00af0a3fe987e517bfab3d19
|
||||
* parent: 409a4412409371db00af0a3fe987e517bfab3d19
|
||||
* commit_url: https://github.com/curl/curl/commit/43157490a5054bd24256fe12876931e8abc9df49
|
||||
* cve: CVE-2021-22945
|
||||
* license: curl
|
||||
* function: mqtt_send
|
||||
* relpath: lib/mqtt.c
|
||||
*/
|
||||
|
||||
static CURLcode mqtt_send(struct Curl_easy *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
|
||||
struct MQTT *mq = data->req.p.mqtt;
|
||||
ssize_t n;
|
||||
result = Curl_write(data, sockfd, buf, len, &n);
|
||||
if(!result)
|
||||
Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
|
||||
if(len != (size_t)n) {
|
||||
size_t nsend = len - n;
|
||||
char *sendleftovers = Curl_memdup(&buf[n], nsend);
|
||||
if(!sendleftovers)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
mq->sendleftovers = sendleftovers;
|
||||
mq->nsend = nsend;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Provenance: curl/curl imap_state_capability_resp (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: imap_state_capability_resp
|
||||
* relpath: lib/imap.c
|
||||
*/
|
||||
|
||||
static CURLcode imap_state_capability_resp(struct Curl_easy *data,
|
||||
int imapcode,
|
||||
imapstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
struct imap_conn *imapc = &conn->proto.imapc;
|
||||
const char *line = data->state.buffer;
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Do we have a untagged response? */
|
||||
if(imapcode == '*') {
|
||||
line += 2;
|
||||
|
||||
/* Loop through the data line */
|
||||
for(;;) {
|
||||
size_t wordlen;
|
||||
while(*line &&
|
||||
(*line == ' ' || *line == '\t' ||
|
||||
*line == '\r' || *line == '\n')) {
|
||||
|
||||
line++;
|
||||
}
|
||||
|
||||
if(!*line)
|
||||
break;
|
||||
|
||||
/* Extract the word */
|
||||
for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
|
||||
line[wordlen] != '\t' && line[wordlen] != '\r' &&
|
||||
line[wordlen] != '\n';)
|
||||
wordlen++;
|
||||
|
||||
/* Does the server support the STARTTLS capability? */
|
||||
if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
|
||||
imapc->tls_supported = TRUE;
|
||||
|
||||
/* Has the server explicitly disabled clear text authentication? */
|
||||
else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
|
||||
imapc->login_disabled = TRUE;
|
||||
|
||||
/* Does the server support the SASL-IR capability? */
|
||||
else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
|
||||
imapc->ir_supported = TRUE;
|
||||
|
||||
/* Do we have a SASL based authentication mechanism? */
|
||||
else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
|
||||
size_t llen;
|
||||
unsigned short mechbit;
|
||||
|
||||
line += 5;
|
||||
wordlen -= 5;
|
||||
|
||||
/* Test the word for a matching authentication mechanism */
|
||||
mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
|
||||
if(mechbit && llen == wordlen)
|
||||
imapc->sasl.authmechs |= mechbit;
|
||||
}
|
||||
|
||||
line += wordlen;
|
||||
}
|
||||
}
|
||||
else if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) {
|
||||
/* PREAUTH is not compatible with STARTTLS. */
|
||||
if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
|
||||
/* Switch to TLS connection now */
|
||||
result = imap_perform_starttls(data, conn);
|
||||
}
|
||||
else if(data->set.use_ssl <= CURLUSESSL_TRY)
|
||||
result = imap_perform_authentication(data, conn);
|
||||
else {
|
||||
failf(data, "STARTTLS not available.");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = imap_perform_authentication(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Provenance: curl/curl imap_state_capability_resp (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: imap_state_capability_resp
|
||||
* relpath: lib/imap.c
|
||||
*/
|
||||
|
||||
static CURLcode imap_state_capability_resp(struct Curl_easy *data,
|
||||
int imapcode,
|
||||
imapstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
struct imap_conn *imapc = &conn->proto.imapc;
|
||||
const char *line = data->state.buffer;
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Do we have a untagged response? */
|
||||
if(imapcode == '*') {
|
||||
line += 2;
|
||||
|
||||
/* Loop through the data line */
|
||||
for(;;) {
|
||||
size_t wordlen;
|
||||
while(*line &&
|
||||
(*line == ' ' || *line == '\t' ||
|
||||
*line == '\r' || *line == '\n')) {
|
||||
|
||||
line++;
|
||||
}
|
||||
|
||||
if(!*line)
|
||||
break;
|
||||
|
||||
/* Extract the word */
|
||||
for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
|
||||
line[wordlen] != '\t' && line[wordlen] != '\r' &&
|
||||
line[wordlen] != '\n';)
|
||||
wordlen++;
|
||||
|
||||
/* Does the server support the STARTTLS capability? */
|
||||
if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
|
||||
imapc->tls_supported = TRUE;
|
||||
|
||||
/* Has the server explicitly disabled clear text authentication? */
|
||||
else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
|
||||
imapc->login_disabled = TRUE;
|
||||
|
||||
/* Does the server support the SASL-IR capability? */
|
||||
else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
|
||||
imapc->ir_supported = TRUE;
|
||||
|
||||
/* Do we have a SASL based authentication mechanism? */
|
||||
else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
|
||||
size_t llen;
|
||||
unsigned short mechbit;
|
||||
|
||||
line += 5;
|
||||
wordlen -= 5;
|
||||
|
||||
/* Test the word for a matching authentication mechanism */
|
||||
mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
|
||||
if(mechbit && llen == wordlen)
|
||||
imapc->sasl.authmechs |= mechbit;
|
||||
}
|
||||
|
||||
line += wordlen;
|
||||
}
|
||||
}
|
||||
else if(imapcode == IMAP_RESP_OK) {
|
||||
if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) {
|
||||
/* We don't have a SSL/TLS connection yet, but SSL is requested */
|
||||
if(imapc->tls_supported)
|
||||
/* Switch to TLS connection now */
|
||||
result = imap_perform_starttls(data, conn);
|
||||
else if(data->set.use_ssl == CURLUSESSL_TRY)
|
||||
/* Fallback and carry on with authentication */
|
||||
result = imap_perform_authentication(data, conn);
|
||||
else {
|
||||
failf(data, "STARTTLS not supported.");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = imap_perform_authentication(data, conn);
|
||||
}
|
||||
else
|
||||
result = imap_perform_authentication(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/* Provenance: curl/curl pop3_state_capa_resp (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: pop3_state_capa_resp
|
||||
* relpath: lib/pop3.c
|
||||
*/
|
||||
|
||||
static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code,
|
||||
pop3state instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
struct pop3_conn *pop3c = &conn->proto.pop3c;
|
||||
const char *line = data->state.buffer;
|
||||
size_t len = strlen(line);
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Do we have a untagged continuation response? */
|
||||
if(pop3code == '*') {
|
||||
/* Does the server support the STLS capability? */
|
||||
if(len >= 4 && !memcmp(line, "STLS", 4))
|
||||
pop3c->tls_supported = TRUE;
|
||||
|
||||
/* Does the server support clear text authentication? */
|
||||
else if(len >= 4 && !memcmp(line, "USER", 4))
|
||||
pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
|
||||
|
||||
/* Does the server support SASL based authentication? */
|
||||
else if(len >= 5 && !memcmp(line, "SASL ", 5)) {
|
||||
pop3c->authtypes |= POP3_TYPE_SASL;
|
||||
|
||||
/* Advance past the SASL keyword */
|
||||
line += 5;
|
||||
len -= 5;
|
||||
|
||||
/* Loop through the data line */
|
||||
for(;;) {
|
||||
size_t llen;
|
||||
size_t wordlen;
|
||||
unsigned short mechbit;
|
||||
|
||||
while(len &&
|
||||
(*line == ' ' || *line == '\t' ||
|
||||
*line == '\r' || *line == '\n')) {
|
||||
|
||||
line++;
|
||||
len--;
|
||||
}
|
||||
|
||||
if(!len)
|
||||
break;
|
||||
|
||||
/* Extract the word */
|
||||
for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
|
||||
line[wordlen] != '\t' && line[wordlen] != '\r' &&
|
||||
line[wordlen] != '\n';)
|
||||
wordlen++;
|
||||
|
||||
/* Test the word for a matching authentication mechanism */
|
||||
mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
|
||||
if(mechbit && llen == wordlen)
|
||||
pop3c->sasl.authmechs |= mechbit;
|
||||
|
||||
line += wordlen;
|
||||
len -= wordlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Clear text is supported when CAPA isn't recognised */
|
||||
if(pop3code != '+')
|
||||
pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
|
||||
|
||||
if(!data->set.use_ssl || conn->ssl[FIRSTSOCKET].use)
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
else if(pop3code == '+' && pop3c->tls_supported)
|
||||
/* Switch to TLS connection now */
|
||||
result = pop3_perform_starttls(data, conn);
|
||||
else if(data->set.use_ssl <= CURLUSESSL_TRY)
|
||||
/* Fallback and carry on with authentication */
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
else {
|
||||
failf(data, "STLS not supported.");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/* Provenance: curl/curl pop3_state_capa_resp (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: pop3_state_capa_resp
|
||||
* relpath: lib/pop3.c
|
||||
*/
|
||||
|
||||
static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code,
|
||||
pop3state instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
struct pop3_conn *pop3c = &conn->proto.pop3c;
|
||||
const char *line = data->state.buffer;
|
||||
size_t len = strlen(line);
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Do we have a untagged continuation response? */
|
||||
if(pop3code == '*') {
|
||||
/* Does the server support the STLS capability? */
|
||||
if(len >= 4 && !memcmp(line, "STLS", 4))
|
||||
pop3c->tls_supported = TRUE;
|
||||
|
||||
/* Does the server support clear text authentication? */
|
||||
else if(len >= 4 && !memcmp(line, "USER", 4))
|
||||
pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
|
||||
|
||||
/* Does the server support SASL based authentication? */
|
||||
else if(len >= 5 && !memcmp(line, "SASL ", 5)) {
|
||||
pop3c->authtypes |= POP3_TYPE_SASL;
|
||||
|
||||
/* Advance past the SASL keyword */
|
||||
line += 5;
|
||||
len -= 5;
|
||||
|
||||
/* Loop through the data line */
|
||||
for(;;) {
|
||||
size_t llen;
|
||||
size_t wordlen;
|
||||
unsigned short mechbit;
|
||||
|
||||
while(len &&
|
||||
(*line == ' ' || *line == '\t' ||
|
||||
*line == '\r' || *line == '\n')) {
|
||||
|
||||
line++;
|
||||
len--;
|
||||
}
|
||||
|
||||
if(!len)
|
||||
break;
|
||||
|
||||
/* Extract the word */
|
||||
for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
|
||||
line[wordlen] != '\t' && line[wordlen] != '\r' &&
|
||||
line[wordlen] != '\n';)
|
||||
wordlen++;
|
||||
|
||||
/* Test the word for a matching authentication mechanism */
|
||||
mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
|
||||
if(mechbit && llen == wordlen)
|
||||
pop3c->sasl.authmechs |= mechbit;
|
||||
|
||||
line += wordlen;
|
||||
len -= wordlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(pop3code == '+') {
|
||||
if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) {
|
||||
/* We don't have a SSL/TLS connection yet, but SSL is requested */
|
||||
if(pop3c->tls_supported)
|
||||
/* Switch to TLS connection now */
|
||||
result = pop3_perform_starttls(data, conn);
|
||||
else if(data->set.use_ssl == CURLUSESSL_TRY)
|
||||
/* Fallback and carry on with authentication */
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
else {
|
||||
failf(data, "STLS not supported.");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
}
|
||||
else {
|
||||
/* Clear text is supported when CAPA isn't recognised */
|
||||
pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
|
||||
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
/* Provenance: curl/curl ftp_statemachine (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: ftp_statemachine
|
||||
* relpath: lib/ftp.c
|
||||
*/
|
||||
|
||||
static CURLcode ftp_statemachine(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
CURLcode result;
|
||||
curl_socket_t sock = conn->sock[FIRSTSOCKET];
|
||||
int ftpcode;
|
||||
struct ftp_conn *ftpc = &conn->proto.ftpc;
|
||||
struct pingpong *pp = &ftpc->pp;
|
||||
static const char ftpauth[][4] = { "SSL", "TLS" };
|
||||
size_t nread = 0;
|
||||
|
||||
if(pp->sendleft)
|
||||
return Curl_pp_flushsend(data, pp);
|
||||
|
||||
result = ftp_readresp(data, sock, pp, &ftpcode, &nread);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(ftpcode) {
|
||||
/* we have now received a full FTP server response */
|
||||
switch(ftpc->state) {
|
||||
case FTP_WAIT220:
|
||||
if(ftpcode == 230) {
|
||||
/* 230 User logged in - already! Take as 220 if TLS required. */
|
||||
if(data->set.use_ssl <= CURLUSESSL_TRY ||
|
||||
conn->bits.ftp_use_control_ssl)
|
||||
return ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
}
|
||||
else if(ftpcode != 220) {
|
||||
failf(data, "Got a %03d ftp-server response when 220 was expected",
|
||||
ftpcode);
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
|
||||
/* We have received a 220 response fine, now we proceed. */
|
||||
#ifdef HAVE_GSSAPI
|
||||
if(data->set.krb) {
|
||||
/* If not anonymous login, try a secure login. Note that this
|
||||
procedure is still BLOCKING. */
|
||||
|
||||
Curl_sec_request_prot(conn, "private");
|
||||
/* We set private first as default, in case the line below fails to
|
||||
set a valid level */
|
||||
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
|
||||
|
||||
if(Curl_sec_login(data, conn))
|
||||
infof(data, "Logging in with password in cleartext!");
|
||||
else
|
||||
infof(data, "Authentication successful");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
|
||||
/* We don't have a SSL/TLS control connection yet, but FTPS is
|
||||
requested. Try a FTPS connection now */
|
||||
|
||||
ftpc->count3 = 0;
|
||||
switch(data->set.ftpsslauth) {
|
||||
case CURLFTPAUTH_DEFAULT:
|
||||
case CURLFTPAUTH_SSL:
|
||||
ftpc->count2 = 1; /* add one to get next */
|
||||
ftpc->count1 = 0;
|
||||
break;
|
||||
case CURLFTPAUTH_TLS:
|
||||
ftpc->count2 = -1; /* subtract one to get next */
|
||||
ftpc->count1 = 1;
|
||||
break;
|
||||
default:
|
||||
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
|
||||
(int)data->set.ftpsslauth);
|
||||
return CURLE_UNKNOWN_OPTION; /* we don't know what to do */
|
||||
}
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
if(!result)
|
||||
state(data, FTP_AUTH);
|
||||
}
|
||||
else
|
||||
result = ftp_state_user(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_AUTH:
|
||||
/* we have gotten the response to a previous AUTH command */
|
||||
|
||||
/* RFC2228 (page 5) says:
|
||||
*
|
||||
* If the server is willing to accept the named security mechanism,
|
||||
* and does not require any security data, it must respond with
|
||||
* reply code 234/334.
|
||||
*/
|
||||
|
||||
if((ftpcode == 234) || (ftpcode == 334)) {
|
||||
/* Curl_ssl_connect is BLOCKING */
|
||||
result = Curl_ssl_connect(data, conn, FIRSTSOCKET);
|
||||
if(!result) {
|
||||
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
|
||||
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
}
|
||||
else if(ftpc->count3 < 1) {
|
||||
ftpc->count3++;
|
||||
ftpc->count1 += ftpc->count2; /* get next attempt */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
/* remain in this same state */
|
||||
}
|
||||
else {
|
||||
if(data->set.use_ssl > CURLUSESSL_TRY)
|
||||
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
else
|
||||
/* ignore the failure and continue */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_USER:
|
||||
case FTP_PASS:
|
||||
result = ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_ACCT:
|
||||
result = ftp_state_acct_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PBSZ:
|
||||
result =
|
||||
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
|
||||
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
|
||||
if(!result)
|
||||
state(data, FTP_PROT);
|
||||
break;
|
||||
|
||||
case FTP_PROT:
|
||||
if(ftpcode/100 == 2)
|
||||
/* We have enabled SSL for the data connection! */
|
||||
conn->bits.ftp_use_data_ssl =
|
||||
(data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE;
|
||||
/* FTP servers typically responds with 500 if they decide to reject
|
||||
our 'P' request */
|
||||
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
|
||||
/* we failed and bails out */
|
||||
return CURLE_USE_SSL_FAILED;
|
||||
|
||||
if(data->set.ftp_ccc) {
|
||||
/* CCC - Clear Command Channel
|
||||
*/
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
|
||||
if(!result)
|
||||
state(data, FTP_CCC);
|
||||
}
|
||||
else
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_CCC:
|
||||
if(ftpcode < 500) {
|
||||
/* First shut down the SSL layer (note: this call will block) */
|
||||
result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET);
|
||||
|
||||
if(result)
|
||||
failf(data, "Failed to clear the command channel (CCC)");
|
||||
}
|
||||
if(!result)
|
||||
/* Then continue as normal */
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PWD:
|
||||
if(ftpcode == 257) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
const size_t buf_size = data->set.buffer_size;
|
||||
char *dir;
|
||||
bool entry_extracted = FALSE;
|
||||
|
||||
dir = malloc(nread + 1);
|
||||
if(!dir)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
257<space>[rubbish]"<directory-name>"<space><commentary> and the
|
||||
RFC959 says
|
||||
|
||||
The directory name can contain any character; embedded
|
||||
double-quotes should be escaped by double-quotes (the
|
||||
"quote-doubling" convention).
|
||||
*/
|
||||
|
||||
/* scan for the first double-quote for non-standard responses */
|
||||
while(ptr < &data->state.buffer[buf_size]
|
||||
&& *ptr != '\n' && *ptr != '\0' && *ptr != '"')
|
||||
ptr++;
|
||||
|
||||
if('\"' == *ptr) {
|
||||
/* it started good */
|
||||
char *store;
|
||||
ptr++;
|
||||
for(store = dir; *ptr;) {
|
||||
if('\"' == *ptr) {
|
||||
if('\"' == ptr[1]) {
|
||||
/* "quote-doubling" */
|
||||
*store = ptr[1];
|
||||
ptr++;
|
||||
}
|
||||
else {
|
||||
/* end of path */
|
||||
entry_extracted = TRUE;
|
||||
break; /* get out of this loop */
|
||||
}
|
||||
}
|
||||
else
|
||||
*store = *ptr;
|
||||
store++;
|
||||
ptr++;
|
||||
}
|
||||
*store = '\0'; /* null-terminate */
|
||||
}
|
||||
if(entry_extracted) {
|
||||
/* If the path name does not look like an absolute path (i.e.: it
|
||||
does not start with a '/'), we probably need some server-dependent
|
||||
adjustments. For example, this is the case when connecting to
|
||||
an OS400 FTP server: this server supports two name syntaxes,
|
||||
the default one being incompatible with standard paths. In
|
||||
addition, this server switches automatically to the regular path
|
||||
syntax when one is encountered in a command: this results in
|
||||
having an entrypath in the wrong syntax when later used in CWD.
|
||||
The method used here is to check the server OS: we do it only
|
||||
if the path name looks strange to minimize overhead on other
|
||||
systems. */
|
||||
|
||||
if(!ftpc->server_os && dir[0] != '/') {
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
|
||||
if(result) {
|
||||
free(dir);
|
||||
return result;
|
||||
}
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
state(data, FTP_SYST);
|
||||
break;
|
||||
}
|
||||
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
}
|
||||
else {
|
||||
/* couldn't get the path */
|
||||
free(dir);
|
||||
infof(data, "Failed to figure out path");
|
||||
}
|
||||
}
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_SYST:
|
||||
if(ftpcode == 215) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
char *os;
|
||||
char *store;
|
||||
|
||||
os = malloc(nread + 1);
|
||||
if(!os)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
215<space><OS-name><space><commentary>
|
||||
*/
|
||||
while(*ptr == ' ')
|
||||
ptr++;
|
||||
for(store = os; *ptr && *ptr != ' ';)
|
||||
*store++ = *ptr++;
|
||||
*store = '\0'; /* null-terminate */
|
||||
|
||||
/* Check for special servers here. */
|
||||
|
||||
if(strcasecompare(os, "OS/400")) {
|
||||
/* Force OS400 name format 1. */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
|
||||
if(result) {
|
||||
free(os);
|
||||
return result;
|
||||
}
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
state(data, FTP_NAMEFMT);
|
||||
break;
|
||||
}
|
||||
/* Nothing special for the target server. */
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
}
|
||||
else {
|
||||
/* Cannot identify server OS. Continue anyway and cross fingers. */
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_NAMEFMT:
|
||||
if(ftpcode == 250) {
|
||||
/* Name format change successful: reload initial path. */
|
||||
ftp_state_pwd(data, conn);
|
||||
break;
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_QUOTE:
|
||||
case FTP_POSTQUOTE:
|
||||
case FTP_RETR_PREQUOTE:
|
||||
case FTP_STOR_PREQUOTE:
|
||||
if((ftpcode >= 400) && !ftpc->count2) {
|
||||
/* failure response code, and not allowed to fail */
|
||||
failf(data, "QUOT command failed with %03d", ftpcode);
|
||||
result = CURLE_QUOTE_ERROR;
|
||||
}
|
||||
else
|
||||
result = ftp_state_quote(data, FALSE, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_CWD:
|
||||
if(ftpcode/100 != 2) {
|
||||
/* failure to CWD there */
|
||||
if(data->set.ftp_create_missing_dirs &&
|
||||
ftpc->cwdcount && !ftpc->count2) {
|
||||
/* try making it */
|
||||
ftpc->count2++; /* counter to prevent CWD-MKD loops */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
if(!result)
|
||||
state(data, FTP_MKD);
|
||||
}
|
||||
else {
|
||||
/* return failure */
|
||||
failf(data, "Server denied you to change to the given directory");
|
||||
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
|
||||
to enter it */
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* success */
|
||||
ftpc->count2 = 0;
|
||||
if(++ftpc->cwdcount <= ftpc->dirdepth)
|
||||
/* send next CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
else
|
||||
result = ftp_state_mdtm(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MKD:
|
||||
if((ftpcode/100 != 2) && !ftpc->count3--) {
|
||||
/* failure to MKD the dir */
|
||||
failf(data, "Failed to MKD dir: %03d", ftpcode);
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
else {
|
||||
state(data, FTP_CWD);
|
||||
/* send CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MDTM:
|
||||
result = ftp_state_mdtm_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_TYPE:
|
||||
case FTP_LIST_TYPE:
|
||||
case FTP_RETR_TYPE:
|
||||
case FTP_STOR_TYPE:
|
||||
result = ftp_state_type_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_SIZE:
|
||||
case FTP_RETR_SIZE:
|
||||
case FTP_STOR_SIZE:
|
||||
result = ftp_state_size_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_REST:
|
||||
case FTP_RETR_REST:
|
||||
result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_PRET:
|
||||
if(ftpcode != 200) {
|
||||
/* there only is this one standard OK return code. */
|
||||
failf(data, "PRET command not accepted: %03d", ftpcode);
|
||||
return CURLE_FTP_PRET_FAILED;
|
||||
}
|
||||
result = ftp_state_use_pasv(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PASV:
|
||||
result = ftp_state_pasv_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PORT:
|
||||
result = ftp_state_port_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_LIST:
|
||||
case FTP_RETR:
|
||||
result = ftp_state_get_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_STOR:
|
||||
result = ftp_state_stor_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_QUIT:
|
||||
/* fallthrough, just stop! */
|
||||
default:
|
||||
/* internal error */
|
||||
state(data, FTP_STOP);
|
||||
break;
|
||||
}
|
||||
} /* if(ftpcode) */
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
/* Provenance: curl/curl ftp_statemachine (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
||||
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* cve: CVE-2021-22946
|
||||
* license: curl
|
||||
* function: ftp_statemachine
|
||||
* relpath: lib/ftp.c
|
||||
*/
|
||||
|
||||
static CURLcode ftp_statemachine(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
CURLcode result;
|
||||
curl_socket_t sock = conn->sock[FIRSTSOCKET];
|
||||
int ftpcode;
|
||||
struct ftp_conn *ftpc = &conn->proto.ftpc;
|
||||
struct pingpong *pp = &ftpc->pp;
|
||||
static const char ftpauth[][4] = { "SSL", "TLS" };
|
||||
size_t nread = 0;
|
||||
|
||||
if(pp->sendleft)
|
||||
return Curl_pp_flushsend(data, pp);
|
||||
|
||||
result = ftp_readresp(data, sock, pp, &ftpcode, &nread);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(ftpcode) {
|
||||
/* we have now received a full FTP server response */
|
||||
switch(ftpc->state) {
|
||||
case FTP_WAIT220:
|
||||
if(ftpcode == 230)
|
||||
/* 230 User logged in - already! */
|
||||
return ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
else if(ftpcode != 220) {
|
||||
failf(data, "Got a %03d ftp-server response when 220 was expected",
|
||||
ftpcode);
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
|
||||
/* We have received a 220 response fine, now we proceed. */
|
||||
#ifdef HAVE_GSSAPI
|
||||
if(data->set.krb) {
|
||||
/* If not anonymous login, try a secure login. Note that this
|
||||
procedure is still BLOCKING. */
|
||||
|
||||
Curl_sec_request_prot(conn, "private");
|
||||
/* We set private first as default, in case the line below fails to
|
||||
set a valid level */
|
||||
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
|
||||
|
||||
if(Curl_sec_login(data, conn))
|
||||
infof(data, "Logging in with password in cleartext!");
|
||||
else
|
||||
infof(data, "Authentication successful");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
|
||||
/* We don't have a SSL/TLS control connection yet, but FTPS is
|
||||
requested. Try a FTPS connection now */
|
||||
|
||||
ftpc->count3 = 0;
|
||||
switch(data->set.ftpsslauth) {
|
||||
case CURLFTPAUTH_DEFAULT:
|
||||
case CURLFTPAUTH_SSL:
|
||||
ftpc->count2 = 1; /* add one to get next */
|
||||
ftpc->count1 = 0;
|
||||
break;
|
||||
case CURLFTPAUTH_TLS:
|
||||
ftpc->count2 = -1; /* subtract one to get next */
|
||||
ftpc->count1 = 1;
|
||||
break;
|
||||
default:
|
||||
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
|
||||
(int)data->set.ftpsslauth);
|
||||
return CURLE_UNKNOWN_OPTION; /* we don't know what to do */
|
||||
}
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
if(!result)
|
||||
state(data, FTP_AUTH);
|
||||
}
|
||||
else
|
||||
result = ftp_state_user(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_AUTH:
|
||||
/* we have gotten the response to a previous AUTH command */
|
||||
|
||||
/* RFC2228 (page 5) says:
|
||||
*
|
||||
* If the server is willing to accept the named security mechanism,
|
||||
* and does not require any security data, it must respond with
|
||||
* reply code 234/334.
|
||||
*/
|
||||
|
||||
if((ftpcode == 234) || (ftpcode == 334)) {
|
||||
/* Curl_ssl_connect is BLOCKING */
|
||||
result = Curl_ssl_connect(data, conn, FIRSTSOCKET);
|
||||
if(!result) {
|
||||
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
|
||||
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
}
|
||||
else if(ftpc->count3 < 1) {
|
||||
ftpc->count3++;
|
||||
ftpc->count1 += ftpc->count2; /* get next attempt */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
/* remain in this same state */
|
||||
}
|
||||
else {
|
||||
if(data->set.use_ssl > CURLUSESSL_TRY)
|
||||
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
else
|
||||
/* ignore the failure and continue */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_USER:
|
||||
case FTP_PASS:
|
||||
result = ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_ACCT:
|
||||
result = ftp_state_acct_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PBSZ:
|
||||
result =
|
||||
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
|
||||
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
|
||||
if(!result)
|
||||
state(data, FTP_PROT);
|
||||
break;
|
||||
|
||||
case FTP_PROT:
|
||||
if(ftpcode/100 == 2)
|
||||
/* We have enabled SSL for the data connection! */
|
||||
conn->bits.ftp_use_data_ssl =
|
||||
(data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE;
|
||||
/* FTP servers typically responds with 500 if they decide to reject
|
||||
our 'P' request */
|
||||
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
|
||||
/* we failed and bails out */
|
||||
return CURLE_USE_SSL_FAILED;
|
||||
|
||||
if(data->set.ftp_ccc) {
|
||||
/* CCC - Clear Command Channel
|
||||
*/
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
|
||||
if(!result)
|
||||
state(data, FTP_CCC);
|
||||
}
|
||||
else
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_CCC:
|
||||
if(ftpcode < 500) {
|
||||
/* First shut down the SSL layer (note: this call will block) */
|
||||
result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET);
|
||||
|
||||
if(result)
|
||||
failf(data, "Failed to clear the command channel (CCC)");
|
||||
}
|
||||
if(!result)
|
||||
/* Then continue as normal */
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PWD:
|
||||
if(ftpcode == 257) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
const size_t buf_size = data->set.buffer_size;
|
||||
char *dir;
|
||||
bool entry_extracted = FALSE;
|
||||
|
||||
dir = malloc(nread + 1);
|
||||
if(!dir)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
257<space>[rubbish]"<directory-name>"<space><commentary> and the
|
||||
RFC959 says
|
||||
|
||||
The directory name can contain any character; embedded
|
||||
double-quotes should be escaped by double-quotes (the
|
||||
"quote-doubling" convention).
|
||||
*/
|
||||
|
||||
/* scan for the first double-quote for non-standard responses */
|
||||
while(ptr < &data->state.buffer[buf_size]
|
||||
&& *ptr != '\n' && *ptr != '\0' && *ptr != '"')
|
||||
ptr++;
|
||||
|
||||
if('\"' == *ptr) {
|
||||
/* it started good */
|
||||
char *store;
|
||||
ptr++;
|
||||
for(store = dir; *ptr;) {
|
||||
if('\"' == *ptr) {
|
||||
if('\"' == ptr[1]) {
|
||||
/* "quote-doubling" */
|
||||
*store = ptr[1];
|
||||
ptr++;
|
||||
}
|
||||
else {
|
||||
/* end of path */
|
||||
entry_extracted = TRUE;
|
||||
break; /* get out of this loop */
|
||||
}
|
||||
}
|
||||
else
|
||||
*store = *ptr;
|
||||
store++;
|
||||
ptr++;
|
||||
}
|
||||
*store = '\0'; /* null-terminate */
|
||||
}
|
||||
if(entry_extracted) {
|
||||
/* If the path name does not look like an absolute path (i.e.: it
|
||||
does not start with a '/'), we probably need some server-dependent
|
||||
adjustments. For example, this is the case when connecting to
|
||||
an OS400 FTP server: this server supports two name syntaxes,
|
||||
the default one being incompatible with standard paths. In
|
||||
addition, this server switches automatically to the regular path
|
||||
syntax when one is encountered in a command: this results in
|
||||
having an entrypath in the wrong syntax when later used in CWD.
|
||||
The method used here is to check the server OS: we do it only
|
||||
if the path name looks strange to minimize overhead on other
|
||||
systems. */
|
||||
|
||||
if(!ftpc->server_os && dir[0] != '/') {
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
|
||||
if(result) {
|
||||
free(dir);
|
||||
return result;
|
||||
}
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
state(data, FTP_SYST);
|
||||
break;
|
||||
}
|
||||
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
}
|
||||
else {
|
||||
/* couldn't get the path */
|
||||
free(dir);
|
||||
infof(data, "Failed to figure out path");
|
||||
}
|
||||
}
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_SYST:
|
||||
if(ftpcode == 215) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
char *os;
|
||||
char *store;
|
||||
|
||||
os = malloc(nread + 1);
|
||||
if(!os)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
215<space><OS-name><space><commentary>
|
||||
*/
|
||||
while(*ptr == ' ')
|
||||
ptr++;
|
||||
for(store = os; *ptr && *ptr != ' ';)
|
||||
*store++ = *ptr++;
|
||||
*store = '\0'; /* null-terminate */
|
||||
|
||||
/* Check for special servers here. */
|
||||
|
||||
if(strcasecompare(os, "OS/400")) {
|
||||
/* Force OS400 name format 1. */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
|
||||
if(result) {
|
||||
free(os);
|
||||
return result;
|
||||
}
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
state(data, FTP_NAMEFMT);
|
||||
break;
|
||||
}
|
||||
/* Nothing special for the target server. */
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
}
|
||||
else {
|
||||
/* Cannot identify server OS. Continue anyway and cross fingers. */
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_NAMEFMT:
|
||||
if(ftpcode == 250) {
|
||||
/* Name format change successful: reload initial path. */
|
||||
ftp_state_pwd(data, conn);
|
||||
break;
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_QUOTE:
|
||||
case FTP_POSTQUOTE:
|
||||
case FTP_RETR_PREQUOTE:
|
||||
case FTP_STOR_PREQUOTE:
|
||||
if((ftpcode >= 400) && !ftpc->count2) {
|
||||
/* failure response code, and not allowed to fail */
|
||||
failf(data, "QUOT command failed with %03d", ftpcode);
|
||||
result = CURLE_QUOTE_ERROR;
|
||||
}
|
||||
else
|
||||
result = ftp_state_quote(data, FALSE, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_CWD:
|
||||
if(ftpcode/100 != 2) {
|
||||
/* failure to CWD there */
|
||||
if(data->set.ftp_create_missing_dirs &&
|
||||
ftpc->cwdcount && !ftpc->count2) {
|
||||
/* try making it */
|
||||
ftpc->count2++; /* counter to prevent CWD-MKD loops */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
if(!result)
|
||||
state(data, FTP_MKD);
|
||||
}
|
||||
else {
|
||||
/* return failure */
|
||||
failf(data, "Server denied you to change to the given directory");
|
||||
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
|
||||
to enter it */
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* success */
|
||||
ftpc->count2 = 0;
|
||||
if(++ftpc->cwdcount <= ftpc->dirdepth)
|
||||
/* send next CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
else
|
||||
result = ftp_state_mdtm(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MKD:
|
||||
if((ftpcode/100 != 2) && !ftpc->count3--) {
|
||||
/* failure to MKD the dir */
|
||||
failf(data, "Failed to MKD dir: %03d", ftpcode);
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
else {
|
||||
state(data, FTP_CWD);
|
||||
/* send CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MDTM:
|
||||
result = ftp_state_mdtm_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_TYPE:
|
||||
case FTP_LIST_TYPE:
|
||||
case FTP_RETR_TYPE:
|
||||
case FTP_STOR_TYPE:
|
||||
result = ftp_state_type_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_SIZE:
|
||||
case FTP_RETR_SIZE:
|
||||
case FTP_STOR_SIZE:
|
||||
result = ftp_state_size_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_REST:
|
||||
case FTP_RETR_REST:
|
||||
result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_PRET:
|
||||
if(ftpcode != 200) {
|
||||
/* there only is this one standard OK return code. */
|
||||
failf(data, "PRET command not accepted: %03d", ftpcode);
|
||||
return CURLE_FTP_PRET_FAILED;
|
||||
}
|
||||
result = ftp_state_use_pasv(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PASV:
|
||||
result = ftp_state_pasv_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PORT:
|
||||
result = ftp_state_port_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_LIST:
|
||||
case FTP_RETR:
|
||||
result = ftp_state_get_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_STOR:
|
||||
result = ftp_state_stor_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_QUIT:
|
||||
/* fallthrough, just stop! */
|
||||
default:
|
||||
/* internal error */
|
||||
state(data, FTP_STOP);
|
||||
break;
|
||||
}
|
||||
} /* if(ftpcode) */
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* Provenance: curl/curl imap_state_starttls_resp (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: imap_state_starttls_resp
|
||||
* relpath: lib/imap.c
|
||||
*/
|
||||
|
||||
static CURLcode imap_state_starttls_resp(struct Curl_easy *data,
|
||||
int imapcode,
|
||||
imapstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Pipelining in response is forbidden. */
|
||||
if(data->conn->proto.imapc.pp.cache_size)
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
|
||||
if(imapcode != IMAP_RESP_OK) {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = imap_perform_authentication(data, conn);
|
||||
}
|
||||
else
|
||||
result = imap_perform_upgrade_tls(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Provenance: curl/curl imap_state_starttls_resp (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: imap_state_starttls_resp
|
||||
* relpath: lib/imap.c
|
||||
*/
|
||||
|
||||
static CURLcode imap_state_starttls_resp(struct Curl_easy *data,
|
||||
int imapcode,
|
||||
imapstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
if(imapcode != IMAP_RESP_OK) {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = imap_perform_authentication(data, conn);
|
||||
}
|
||||
else
|
||||
result = imap_perform_upgrade_tls(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Provenance: curl/curl pop3_state_starttls_resp (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: pop3_state_starttls_resp
|
||||
* relpath: lib/pop3.c
|
||||
*/
|
||||
|
||||
static CURLcode pop3_state_starttls_resp(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
int pop3code,
|
||||
pop3state instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Pipelining in response is forbidden. */
|
||||
if(data->conn->proto.pop3c.pp.cache_size)
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
|
||||
if(pop3code != '+') {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
}
|
||||
else
|
||||
result = pop3_perform_upgrade_tls(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Provenance: curl/curl pop3_state_starttls_resp (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: pop3_state_starttls_resp
|
||||
* relpath: lib/pop3.c
|
||||
*/
|
||||
|
||||
static CURLcode pop3_state_starttls_resp(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
int pop3code,
|
||||
pop3state instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
if(pop3code != '+') {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied");
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = pop3_perform_authentication(data, conn);
|
||||
}
|
||||
else
|
||||
result = pop3_perform_upgrade_tls(data, conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Provenance: curl/curl smtp_state_starttls_resp (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: smtp_state_starttls_resp
|
||||
* relpath: lib/smtp.c
|
||||
*/
|
||||
|
||||
static CURLcode smtp_state_starttls_resp(struct Curl_easy *data,
|
||||
int smtpcode,
|
||||
smtpstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
/* Pipelining in response is forbidden. */
|
||||
if(data->conn->proto.smtpc.pp.cache_size)
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
|
||||
if(smtpcode != 220) {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied, code %d", smtpcode);
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = smtp_perform_authentication(data);
|
||||
}
|
||||
else
|
||||
result = smtp_perform_upgrade_tls(data);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Provenance: curl/curl smtp_state_starttls_resp (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: smtp_state_starttls_resp
|
||||
* relpath: lib/smtp.c
|
||||
*/
|
||||
|
||||
static CURLcode smtp_state_starttls_resp(struct Curl_easy *data,
|
||||
int smtpcode,
|
||||
smtpstate instate)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
(void)instate; /* no use for this yet */
|
||||
|
||||
if(smtpcode != 220) {
|
||||
if(data->set.use_ssl != CURLUSESSL_TRY) {
|
||||
failf(data, "STARTTLS denied, code %d", smtpcode);
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
}
|
||||
else
|
||||
result = smtp_perform_authentication(data);
|
||||
}
|
||||
else
|
||||
result = smtp_perform_upgrade_tls(data);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
/* Provenance: curl/curl ftp_statemachine (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: ftp_statemachine
|
||||
* relpath: lib/ftp.c
|
||||
*/
|
||||
|
||||
static CURLcode ftp_statemachine(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
CURLcode result;
|
||||
curl_socket_t sock = conn->sock[FIRSTSOCKET];
|
||||
int ftpcode;
|
||||
struct ftp_conn *ftpc = &conn->proto.ftpc;
|
||||
struct pingpong *pp = &ftpc->pp;
|
||||
static const char ftpauth[][4] = { "SSL", "TLS" };
|
||||
size_t nread = 0;
|
||||
|
||||
if(pp->sendleft)
|
||||
return Curl_pp_flushsend(data, pp);
|
||||
|
||||
result = ftp_readresp(data, sock, pp, &ftpcode, &nread);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(ftpcode) {
|
||||
/* we have now received a full FTP server response */
|
||||
switch(ftpc->state) {
|
||||
case FTP_WAIT220:
|
||||
if(ftpcode == 230) {
|
||||
/* 230 User logged in - already! Take as 220 if TLS required. */
|
||||
if(data->set.use_ssl <= CURLUSESSL_TRY ||
|
||||
conn->bits.ftp_use_control_ssl)
|
||||
return ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
}
|
||||
else if(ftpcode != 220) {
|
||||
failf(data, "Got a %03d ftp-server response when 220 was expected",
|
||||
ftpcode);
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
|
||||
/* We have received a 220 response fine, now we proceed. */
|
||||
#ifdef HAVE_GSSAPI
|
||||
if(data->set.krb) {
|
||||
/* If not anonymous login, try a secure login. Note that this
|
||||
procedure is still BLOCKING. */
|
||||
|
||||
Curl_sec_request_prot(conn, "private");
|
||||
/* We set private first as default, in case the line below fails to
|
||||
set a valid level */
|
||||
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
|
||||
|
||||
if(Curl_sec_login(data, conn))
|
||||
infof(data, "Logging in with password in cleartext!");
|
||||
else
|
||||
infof(data, "Authentication successful");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
|
||||
/* We don't have a SSL/TLS control connection yet, but FTPS is
|
||||
requested. Try a FTPS connection now */
|
||||
|
||||
ftpc->count3 = 0;
|
||||
switch(data->set.ftpsslauth) {
|
||||
case CURLFTPAUTH_DEFAULT:
|
||||
case CURLFTPAUTH_SSL:
|
||||
ftpc->count2 = 1; /* add one to get next */
|
||||
ftpc->count1 = 0;
|
||||
break;
|
||||
case CURLFTPAUTH_TLS:
|
||||
ftpc->count2 = -1; /* subtract one to get next */
|
||||
ftpc->count1 = 1;
|
||||
break;
|
||||
default:
|
||||
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
|
||||
(int)data->set.ftpsslauth);
|
||||
return CURLE_UNKNOWN_OPTION; /* we don't know what to do */
|
||||
}
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
if(!result)
|
||||
state(data, FTP_AUTH);
|
||||
}
|
||||
else
|
||||
result = ftp_state_user(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_AUTH:
|
||||
/* we have gotten the response to a previous AUTH command */
|
||||
|
||||
if(pp->cache_size)
|
||||
return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */
|
||||
|
||||
/* RFC2228 (page 5) says:
|
||||
*
|
||||
* If the server is willing to accept the named security mechanism,
|
||||
* and does not require any security data, it must respond with
|
||||
* reply code 234/334.
|
||||
*/
|
||||
|
||||
if((ftpcode == 234) || (ftpcode == 334)) {
|
||||
/* Curl_ssl_connect is BLOCKING */
|
||||
result = Curl_ssl_connect(data, conn, FIRSTSOCKET);
|
||||
if(!result) {
|
||||
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
|
||||
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
}
|
||||
else if(ftpc->count3 < 1) {
|
||||
ftpc->count3++;
|
||||
ftpc->count1 += ftpc->count2; /* get next attempt */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
/* remain in this same state */
|
||||
}
|
||||
else {
|
||||
if(data->set.use_ssl > CURLUSESSL_TRY)
|
||||
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
else
|
||||
/* ignore the failure and continue */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_USER:
|
||||
case FTP_PASS:
|
||||
result = ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_ACCT:
|
||||
result = ftp_state_acct_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PBSZ:
|
||||
result =
|
||||
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
|
||||
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
|
||||
if(!result)
|
||||
state(data, FTP_PROT);
|
||||
break;
|
||||
|
||||
case FTP_PROT:
|
||||
if(ftpcode/100 == 2)
|
||||
/* We have enabled SSL for the data connection! */
|
||||
conn->bits.ftp_use_data_ssl =
|
||||
(data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE;
|
||||
/* FTP servers typically responds with 500 if they decide to reject
|
||||
our 'P' request */
|
||||
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
|
||||
/* we failed and bails out */
|
||||
return CURLE_USE_SSL_FAILED;
|
||||
|
||||
if(data->set.ftp_ccc) {
|
||||
/* CCC - Clear Command Channel
|
||||
*/
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
|
||||
if(!result)
|
||||
state(data, FTP_CCC);
|
||||
}
|
||||
else
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_CCC:
|
||||
if(ftpcode < 500) {
|
||||
/* First shut down the SSL layer (note: this call will block) */
|
||||
result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET);
|
||||
|
||||
if(result)
|
||||
failf(data, "Failed to clear the command channel (CCC)");
|
||||
}
|
||||
if(!result)
|
||||
/* Then continue as normal */
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PWD:
|
||||
if(ftpcode == 257) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
const size_t buf_size = data->set.buffer_size;
|
||||
char *dir;
|
||||
bool entry_extracted = FALSE;
|
||||
|
||||
dir = malloc(nread + 1);
|
||||
if(!dir)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
257<space>[rubbish]"<directory-name>"<space><commentary> and the
|
||||
RFC959 says
|
||||
|
||||
The directory name can contain any character; embedded
|
||||
double-quotes should be escaped by double-quotes (the
|
||||
"quote-doubling" convention).
|
||||
*/
|
||||
|
||||
/* scan for the first double-quote for non-standard responses */
|
||||
while(ptr < &data->state.buffer[buf_size]
|
||||
&& *ptr != '\n' && *ptr != '\0' && *ptr != '"')
|
||||
ptr++;
|
||||
|
||||
if('\"' == *ptr) {
|
||||
/* it started good */
|
||||
char *store;
|
||||
ptr++;
|
||||
for(store = dir; *ptr;) {
|
||||
if('\"' == *ptr) {
|
||||
if('\"' == ptr[1]) {
|
||||
/* "quote-doubling" */
|
||||
*store = ptr[1];
|
||||
ptr++;
|
||||
}
|
||||
else {
|
||||
/* end of path */
|
||||
entry_extracted = TRUE;
|
||||
break; /* get out of this loop */
|
||||
}
|
||||
}
|
||||
else
|
||||
*store = *ptr;
|
||||
store++;
|
||||
ptr++;
|
||||
}
|
||||
*store = '\0'; /* null-terminate */
|
||||
}
|
||||
if(entry_extracted) {
|
||||
/* If the path name does not look like an absolute path (i.e.: it
|
||||
does not start with a '/'), we probably need some server-dependent
|
||||
adjustments. For example, this is the case when connecting to
|
||||
an OS400 FTP server: this server supports two name syntaxes,
|
||||
the default one being incompatible with standard paths. In
|
||||
addition, this server switches automatically to the regular path
|
||||
syntax when one is encountered in a command: this results in
|
||||
having an entrypath in the wrong syntax when later used in CWD.
|
||||
The method used here is to check the server OS: we do it only
|
||||
if the path name looks strange to minimize overhead on other
|
||||
systems. */
|
||||
|
||||
if(!ftpc->server_os && dir[0] != '/') {
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
|
||||
if(result) {
|
||||
free(dir);
|
||||
return result;
|
||||
}
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
state(data, FTP_SYST);
|
||||
break;
|
||||
}
|
||||
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
}
|
||||
else {
|
||||
/* couldn't get the path */
|
||||
free(dir);
|
||||
infof(data, "Failed to figure out path");
|
||||
}
|
||||
}
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_SYST:
|
||||
if(ftpcode == 215) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
char *os;
|
||||
char *store;
|
||||
|
||||
os = malloc(nread + 1);
|
||||
if(!os)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
215<space><OS-name><space><commentary>
|
||||
*/
|
||||
while(*ptr == ' ')
|
||||
ptr++;
|
||||
for(store = os; *ptr && *ptr != ' ';)
|
||||
*store++ = *ptr++;
|
||||
*store = '\0'; /* null-terminate */
|
||||
|
||||
/* Check for special servers here. */
|
||||
|
||||
if(strcasecompare(os, "OS/400")) {
|
||||
/* Force OS400 name format 1. */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
|
||||
if(result) {
|
||||
free(os);
|
||||
return result;
|
||||
}
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
state(data, FTP_NAMEFMT);
|
||||
break;
|
||||
}
|
||||
/* Nothing special for the target server. */
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
}
|
||||
else {
|
||||
/* Cannot identify server OS. Continue anyway and cross fingers. */
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_NAMEFMT:
|
||||
if(ftpcode == 250) {
|
||||
/* Name format change successful: reload initial path. */
|
||||
ftp_state_pwd(data, conn);
|
||||
break;
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_QUOTE:
|
||||
case FTP_POSTQUOTE:
|
||||
case FTP_RETR_PREQUOTE:
|
||||
case FTP_STOR_PREQUOTE:
|
||||
if((ftpcode >= 400) && !ftpc->count2) {
|
||||
/* failure response code, and not allowed to fail */
|
||||
failf(data, "QUOT command failed with %03d", ftpcode);
|
||||
result = CURLE_QUOTE_ERROR;
|
||||
}
|
||||
else
|
||||
result = ftp_state_quote(data, FALSE, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_CWD:
|
||||
if(ftpcode/100 != 2) {
|
||||
/* failure to CWD there */
|
||||
if(data->set.ftp_create_missing_dirs &&
|
||||
ftpc->cwdcount && !ftpc->count2) {
|
||||
/* try making it */
|
||||
ftpc->count2++; /* counter to prevent CWD-MKD loops */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
if(!result)
|
||||
state(data, FTP_MKD);
|
||||
}
|
||||
else {
|
||||
/* return failure */
|
||||
failf(data, "Server denied you to change to the given directory");
|
||||
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
|
||||
to enter it */
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* success */
|
||||
ftpc->count2 = 0;
|
||||
if(++ftpc->cwdcount <= ftpc->dirdepth)
|
||||
/* send next CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
else
|
||||
result = ftp_state_mdtm(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MKD:
|
||||
if((ftpcode/100 != 2) && !ftpc->count3--) {
|
||||
/* failure to MKD the dir */
|
||||
failf(data, "Failed to MKD dir: %03d", ftpcode);
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
else {
|
||||
state(data, FTP_CWD);
|
||||
/* send CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MDTM:
|
||||
result = ftp_state_mdtm_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_TYPE:
|
||||
case FTP_LIST_TYPE:
|
||||
case FTP_RETR_TYPE:
|
||||
case FTP_STOR_TYPE:
|
||||
result = ftp_state_type_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_SIZE:
|
||||
case FTP_RETR_SIZE:
|
||||
case FTP_STOR_SIZE:
|
||||
result = ftp_state_size_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_REST:
|
||||
case FTP_RETR_REST:
|
||||
result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_PRET:
|
||||
if(ftpcode != 200) {
|
||||
/* there only is this one standard OK return code. */
|
||||
failf(data, "PRET command not accepted: %03d", ftpcode);
|
||||
return CURLE_FTP_PRET_FAILED;
|
||||
}
|
||||
result = ftp_state_use_pasv(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PASV:
|
||||
result = ftp_state_pasv_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PORT:
|
||||
result = ftp_state_port_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_LIST:
|
||||
case FTP_RETR:
|
||||
result = ftp_state_get_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_STOR:
|
||||
result = ftp_state_stor_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_QUIT:
|
||||
/* fallthrough, just stop! */
|
||||
default:
|
||||
/* internal error */
|
||||
state(data, FTP_STOP);
|
||||
break;
|
||||
}
|
||||
} /* if(ftpcode) */
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
/* Provenance: curl/curl ftp_statemachine (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* parent: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
||||
* commit_url: https://github.com/curl/curl/commit/8ef147c43646e91fdaad5d0e7b60351f842e5c68
|
||||
* cve: CVE-2021-22947
|
||||
* license: curl
|
||||
* function: ftp_statemachine
|
||||
* relpath: lib/ftp.c
|
||||
*/
|
||||
|
||||
static CURLcode ftp_statemachine(struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
CURLcode result;
|
||||
curl_socket_t sock = conn->sock[FIRSTSOCKET];
|
||||
int ftpcode;
|
||||
struct ftp_conn *ftpc = &conn->proto.ftpc;
|
||||
struct pingpong *pp = &ftpc->pp;
|
||||
static const char ftpauth[][4] = { "SSL", "TLS" };
|
||||
size_t nread = 0;
|
||||
|
||||
if(pp->sendleft)
|
||||
return Curl_pp_flushsend(data, pp);
|
||||
|
||||
result = ftp_readresp(data, sock, pp, &ftpcode, &nread);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(ftpcode) {
|
||||
/* we have now received a full FTP server response */
|
||||
switch(ftpc->state) {
|
||||
case FTP_WAIT220:
|
||||
if(ftpcode == 230) {
|
||||
/* 230 User logged in - already! Take as 220 if TLS required. */
|
||||
if(data->set.use_ssl <= CURLUSESSL_TRY ||
|
||||
conn->bits.ftp_use_control_ssl)
|
||||
return ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
}
|
||||
else if(ftpcode != 220) {
|
||||
failf(data, "Got a %03d ftp-server response when 220 was expected",
|
||||
ftpcode);
|
||||
return CURLE_WEIRD_SERVER_REPLY;
|
||||
}
|
||||
|
||||
/* We have received a 220 response fine, now we proceed. */
|
||||
#ifdef HAVE_GSSAPI
|
||||
if(data->set.krb) {
|
||||
/* If not anonymous login, try a secure login. Note that this
|
||||
procedure is still BLOCKING. */
|
||||
|
||||
Curl_sec_request_prot(conn, "private");
|
||||
/* We set private first as default, in case the line below fails to
|
||||
set a valid level */
|
||||
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
|
||||
|
||||
if(Curl_sec_login(data, conn))
|
||||
infof(data, "Logging in with password in cleartext!");
|
||||
else
|
||||
infof(data, "Authentication successful");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
|
||||
/* We don't have a SSL/TLS control connection yet, but FTPS is
|
||||
requested. Try a FTPS connection now */
|
||||
|
||||
ftpc->count3 = 0;
|
||||
switch(data->set.ftpsslauth) {
|
||||
case CURLFTPAUTH_DEFAULT:
|
||||
case CURLFTPAUTH_SSL:
|
||||
ftpc->count2 = 1; /* add one to get next */
|
||||
ftpc->count1 = 0;
|
||||
break;
|
||||
case CURLFTPAUTH_TLS:
|
||||
ftpc->count2 = -1; /* subtract one to get next */
|
||||
ftpc->count1 = 1;
|
||||
break;
|
||||
default:
|
||||
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
|
||||
(int)data->set.ftpsslauth);
|
||||
return CURLE_UNKNOWN_OPTION; /* we don't know what to do */
|
||||
}
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
if(!result)
|
||||
state(data, FTP_AUTH);
|
||||
}
|
||||
else
|
||||
result = ftp_state_user(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_AUTH:
|
||||
/* we have gotten the response to a previous AUTH command */
|
||||
|
||||
/* RFC2228 (page 5) says:
|
||||
*
|
||||
* If the server is willing to accept the named security mechanism,
|
||||
* and does not require any security data, it must respond with
|
||||
* reply code 234/334.
|
||||
*/
|
||||
|
||||
if((ftpcode == 234) || (ftpcode == 334)) {
|
||||
/* Curl_ssl_connect is BLOCKING */
|
||||
result = Curl_ssl_connect(data, conn, FIRSTSOCKET);
|
||||
if(!result) {
|
||||
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
|
||||
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
}
|
||||
else if(ftpc->count3 < 1) {
|
||||
ftpc->count3++;
|
||||
ftpc->count1 += ftpc->count2; /* get next attempt */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
||||
ftpauth[ftpc->count1]);
|
||||
/* remain in this same state */
|
||||
}
|
||||
else {
|
||||
if(data->set.use_ssl > CURLUSESSL_TRY)
|
||||
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
|
||||
result = CURLE_USE_SSL_FAILED;
|
||||
else
|
||||
/* ignore the failure and continue */
|
||||
result = ftp_state_user(data, conn);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_USER:
|
||||
case FTP_PASS:
|
||||
result = ftp_state_user_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_ACCT:
|
||||
result = ftp_state_acct_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PBSZ:
|
||||
result =
|
||||
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
|
||||
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
|
||||
if(!result)
|
||||
state(data, FTP_PROT);
|
||||
break;
|
||||
|
||||
case FTP_PROT:
|
||||
if(ftpcode/100 == 2)
|
||||
/* We have enabled SSL for the data connection! */
|
||||
conn->bits.ftp_use_data_ssl =
|
||||
(data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE;
|
||||
/* FTP servers typically responds with 500 if they decide to reject
|
||||
our 'P' request */
|
||||
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
|
||||
/* we failed and bails out */
|
||||
return CURLE_USE_SSL_FAILED;
|
||||
|
||||
if(data->set.ftp_ccc) {
|
||||
/* CCC - Clear Command Channel
|
||||
*/
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
|
||||
if(!result)
|
||||
state(data, FTP_CCC);
|
||||
}
|
||||
else
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_CCC:
|
||||
if(ftpcode < 500) {
|
||||
/* First shut down the SSL layer (note: this call will block) */
|
||||
result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET);
|
||||
|
||||
if(result)
|
||||
failf(data, "Failed to clear the command channel (CCC)");
|
||||
}
|
||||
if(!result)
|
||||
/* Then continue as normal */
|
||||
result = ftp_state_pwd(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PWD:
|
||||
if(ftpcode == 257) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
const size_t buf_size = data->set.buffer_size;
|
||||
char *dir;
|
||||
bool entry_extracted = FALSE;
|
||||
|
||||
dir = malloc(nread + 1);
|
||||
if(!dir)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
257<space>[rubbish]"<directory-name>"<space><commentary> and the
|
||||
RFC959 says
|
||||
|
||||
The directory name can contain any character; embedded
|
||||
double-quotes should be escaped by double-quotes (the
|
||||
"quote-doubling" convention).
|
||||
*/
|
||||
|
||||
/* scan for the first double-quote for non-standard responses */
|
||||
while(ptr < &data->state.buffer[buf_size]
|
||||
&& *ptr != '\n' && *ptr != '\0' && *ptr != '"')
|
||||
ptr++;
|
||||
|
||||
if('\"' == *ptr) {
|
||||
/* it started good */
|
||||
char *store;
|
||||
ptr++;
|
||||
for(store = dir; *ptr;) {
|
||||
if('\"' == *ptr) {
|
||||
if('\"' == ptr[1]) {
|
||||
/* "quote-doubling" */
|
||||
*store = ptr[1];
|
||||
ptr++;
|
||||
}
|
||||
else {
|
||||
/* end of path */
|
||||
entry_extracted = TRUE;
|
||||
break; /* get out of this loop */
|
||||
}
|
||||
}
|
||||
else
|
||||
*store = *ptr;
|
||||
store++;
|
||||
ptr++;
|
||||
}
|
||||
*store = '\0'; /* null-terminate */
|
||||
}
|
||||
if(entry_extracted) {
|
||||
/* If the path name does not look like an absolute path (i.e.: it
|
||||
does not start with a '/'), we probably need some server-dependent
|
||||
adjustments. For example, this is the case when connecting to
|
||||
an OS400 FTP server: this server supports two name syntaxes,
|
||||
the default one being incompatible with standard paths. In
|
||||
addition, this server switches automatically to the regular path
|
||||
syntax when one is encountered in a command: this results in
|
||||
having an entrypath in the wrong syntax when later used in CWD.
|
||||
The method used here is to check the server OS: we do it only
|
||||
if the path name looks strange to minimize overhead on other
|
||||
systems. */
|
||||
|
||||
if(!ftpc->server_os && dir[0] != '/') {
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
|
||||
if(result) {
|
||||
free(dir);
|
||||
return result;
|
||||
}
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
state(data, FTP_SYST);
|
||||
break;
|
||||
}
|
||||
|
||||
Curl_safefree(ftpc->entrypath);
|
||||
ftpc->entrypath = dir; /* remember this */
|
||||
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
||||
/* also save it where getinfo can access it: */
|
||||
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
||||
}
|
||||
else {
|
||||
/* couldn't get the path */
|
||||
free(dir);
|
||||
infof(data, "Failed to figure out path");
|
||||
}
|
||||
}
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_SYST:
|
||||
if(ftpcode == 215) {
|
||||
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
||||
char *os;
|
||||
char *store;
|
||||
|
||||
os = malloc(nread + 1);
|
||||
if(!os)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
/* Reply format is like
|
||||
215<space><OS-name><space><commentary>
|
||||
*/
|
||||
while(*ptr == ' ')
|
||||
ptr++;
|
||||
for(store = os; *ptr && *ptr != ' ';)
|
||||
*store++ = *ptr++;
|
||||
*store = '\0'; /* null-terminate */
|
||||
|
||||
/* Check for special servers here. */
|
||||
|
||||
if(strcasecompare(os, "OS/400")) {
|
||||
/* Force OS400 name format 1. */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
|
||||
if(result) {
|
||||
free(os);
|
||||
return result;
|
||||
}
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
state(data, FTP_NAMEFMT);
|
||||
break;
|
||||
}
|
||||
/* Nothing special for the target server. */
|
||||
/* remember target server OS */
|
||||
Curl_safefree(ftpc->server_os);
|
||||
ftpc->server_os = os;
|
||||
}
|
||||
else {
|
||||
/* Cannot identify server OS. Continue anyway and cross fingers. */
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_NAMEFMT:
|
||||
if(ftpcode == 250) {
|
||||
/* Name format change successful: reload initial path. */
|
||||
ftp_state_pwd(data, conn);
|
||||
break;
|
||||
}
|
||||
|
||||
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
||||
DEBUGF(infof(data, "protocol connect phase DONE"));
|
||||
break;
|
||||
|
||||
case FTP_QUOTE:
|
||||
case FTP_POSTQUOTE:
|
||||
case FTP_RETR_PREQUOTE:
|
||||
case FTP_STOR_PREQUOTE:
|
||||
if((ftpcode >= 400) && !ftpc->count2) {
|
||||
/* failure response code, and not allowed to fail */
|
||||
failf(data, "QUOT command failed with %03d", ftpcode);
|
||||
result = CURLE_QUOTE_ERROR;
|
||||
}
|
||||
else
|
||||
result = ftp_state_quote(data, FALSE, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_CWD:
|
||||
if(ftpcode/100 != 2) {
|
||||
/* failure to CWD there */
|
||||
if(data->set.ftp_create_missing_dirs &&
|
||||
ftpc->cwdcount && !ftpc->count2) {
|
||||
/* try making it */
|
||||
ftpc->count2++; /* counter to prevent CWD-MKD loops */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
if(!result)
|
||||
state(data, FTP_MKD);
|
||||
}
|
||||
else {
|
||||
/* return failure */
|
||||
failf(data, "Server denied you to change to the given directory");
|
||||
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
|
||||
to enter it */
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* success */
|
||||
ftpc->count2 = 0;
|
||||
if(++ftpc->cwdcount <= ftpc->dirdepth)
|
||||
/* send next CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
else
|
||||
result = ftp_state_mdtm(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MKD:
|
||||
if((ftpcode/100 != 2) && !ftpc->count3--) {
|
||||
/* failure to MKD the dir */
|
||||
failf(data, "Failed to MKD dir: %03d", ftpcode);
|
||||
result = CURLE_REMOTE_ACCESS_DENIED;
|
||||
}
|
||||
else {
|
||||
state(data, FTP_CWD);
|
||||
/* send CWD */
|
||||
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
||||
ftpc->dirs[ftpc->cwdcount - 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FTP_MDTM:
|
||||
result = ftp_state_mdtm_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_TYPE:
|
||||
case FTP_LIST_TYPE:
|
||||
case FTP_RETR_TYPE:
|
||||
case FTP_STOR_TYPE:
|
||||
result = ftp_state_type_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_SIZE:
|
||||
case FTP_RETR_SIZE:
|
||||
case FTP_STOR_SIZE:
|
||||
result = ftp_state_size_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_REST:
|
||||
case FTP_RETR_REST:
|
||||
result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_PRET:
|
||||
if(ftpcode != 200) {
|
||||
/* there only is this one standard OK return code. */
|
||||
failf(data, "PRET command not accepted: %03d", ftpcode);
|
||||
return CURLE_FTP_PRET_FAILED;
|
||||
}
|
||||
result = ftp_state_use_pasv(data, conn);
|
||||
break;
|
||||
|
||||
case FTP_PASV:
|
||||
result = ftp_state_pasv_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_PORT:
|
||||
result = ftp_state_port_resp(data, ftpcode);
|
||||
break;
|
||||
|
||||
case FTP_LIST:
|
||||
case FTP_RETR:
|
||||
result = ftp_state_get_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_STOR:
|
||||
result = ftp_state_stor_resp(data, ftpcode, ftpc->state);
|
||||
break;
|
||||
|
||||
case FTP_QUIT:
|
||||
/* fallthrough, just stop! */
|
||||
default:
|
||||
/* internal error */
|
||||
state(data, FTP_STOP);
|
||||
break;
|
||||
}
|
||||
} /* if(ftpcode) */
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Provenance: curl/curl conn_free (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* parent: ff2f3e836702daa82e2356195482fcc95f4257dc
|
||||
* commit_url: https://github.com/curl/curl/commit/852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* cve: CVE-2022-22576
|
||||
* license: curl
|
||||
* function: conn_free
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
static void conn_free(struct connectdata *conn)
|
||||
{
|
||||
DEBUGASSERT(conn);
|
||||
|
||||
Curl_free_idnconverted_hostname(&conn->host);
|
||||
Curl_free_idnconverted_hostname(&conn->conn_to_host);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
Curl_free_idnconverted_hostname(&conn->http_proxy.host);
|
||||
Curl_free_idnconverted_hostname(&conn->socks_proxy.host);
|
||||
Curl_safefree(conn->http_proxy.user);
|
||||
Curl_safefree(conn->socks_proxy.user);
|
||||
Curl_safefree(conn->http_proxy.passwd);
|
||||
Curl_safefree(conn->socks_proxy.passwd);
|
||||
Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */
|
||||
Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */
|
||||
Curl_free_primary_ssl_config(&conn->proxy_ssl_config);
|
||||
#endif
|
||||
Curl_safefree(conn->user);
|
||||
Curl_safefree(conn->passwd);
|
||||
Curl_safefree(conn->sasl_authzid);
|
||||
Curl_safefree(conn->options);
|
||||
Curl_safefree(conn->oauth_bearer);
|
||||
Curl_dyn_free(&conn->trailer);
|
||||
Curl_safefree(conn->host.rawalloc); /* host name buffer */
|
||||
Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
|
||||
Curl_safefree(conn->hostname_resolve);
|
||||
Curl_safefree(conn->secondaryhostname);
|
||||
Curl_safefree(conn->connect_state);
|
||||
|
||||
conn_reset_all_postponed_data(conn);
|
||||
Curl_llist_destroy(&conn->easyq, NULL);
|
||||
Curl_safefree(conn->localdev);
|
||||
Curl_free_primary_ssl_config(&conn->ssl_config);
|
||||
|
||||
#ifdef USE_UNIX_SOCKETS
|
||||
Curl_safefree(conn->unix_domain_socket);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSL
|
||||
Curl_safefree(conn->ssl_extra);
|
||||
#endif
|
||||
free(conn); /* free all the connection oriented data */
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* Provenance: curl/curl conn_free (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: ff2f3e836702daa82e2356195482fcc95f4257dc
|
||||
* parent: ff2f3e836702daa82e2356195482fcc95f4257dc
|
||||
* commit_url: https://github.com/curl/curl/commit/852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* cve: CVE-2022-22576
|
||||
* license: curl
|
||||
* function: conn_free
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
static void conn_free(struct connectdata *conn)
|
||||
{
|
||||
DEBUGASSERT(conn);
|
||||
|
||||
Curl_free_idnconverted_hostname(&conn->host);
|
||||
Curl_free_idnconverted_hostname(&conn->conn_to_host);
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
Curl_free_idnconverted_hostname(&conn->http_proxy.host);
|
||||
Curl_free_idnconverted_hostname(&conn->socks_proxy.host);
|
||||
Curl_safefree(conn->http_proxy.user);
|
||||
Curl_safefree(conn->socks_proxy.user);
|
||||
Curl_safefree(conn->http_proxy.passwd);
|
||||
Curl_safefree(conn->socks_proxy.passwd);
|
||||
Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */
|
||||
Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */
|
||||
Curl_free_primary_ssl_config(&conn->proxy_ssl_config);
|
||||
#endif
|
||||
Curl_safefree(conn->user);
|
||||
Curl_safefree(conn->passwd);
|
||||
Curl_safefree(conn->sasl_authzid);
|
||||
Curl_safefree(conn->options);
|
||||
Curl_dyn_free(&conn->trailer);
|
||||
Curl_safefree(conn->host.rawalloc); /* host name buffer */
|
||||
Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
|
||||
Curl_safefree(conn->hostname_resolve);
|
||||
Curl_safefree(conn->secondaryhostname);
|
||||
Curl_safefree(conn->connect_state);
|
||||
|
||||
conn_reset_all_postponed_data(conn);
|
||||
Curl_llist_destroy(&conn->easyq, NULL);
|
||||
Curl_safefree(conn->localdev);
|
||||
Curl_free_primary_ssl_config(&conn->ssl_config);
|
||||
|
||||
#ifdef USE_UNIX_SOCKETS
|
||||
Curl_safefree(conn->unix_domain_socket);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSL
|
||||
Curl_safefree(conn->ssl_extra);
|
||||
#endif
|
||||
free(conn); /* free all the connection oriented data */
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/* Provenance: curl/curl Curl_follow (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 620ea21410030a9977396b4661806bc187231b79
|
||||
* parent: 08b8ef4e726ba10f45081ecda5b3cea788d3c839
|
||||
* commit_url: https://github.com/curl/curl/commit/620ea21410030a9977396b4661806bc187231b79
|
||||
* cve: CVE-2022-27774
|
||||
* license: curl
|
||||
* function: Curl_follow
|
||||
* relpath: lib/transfer.c
|
||||
*/
|
||||
|
||||
* Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string
|
||||
* as given by the remote server and set up the new URL to request.
|
||||
*
|
||||
* This function DOES NOT FREE the given url.
|
||||
*/
|
||||
CURLcode Curl_follow(struct Curl_easy *data,
|
||||
char *newurl, /* the Location: string */
|
||||
followtype type) /* see transfer.h */
|
||||
{
|
||||
#ifdef CURL_DISABLE_HTTP
|
||||
(void)data;
|
||||
(void)newurl;
|
||||
(void)type;
|
||||
/* Location: following will not happen when HTTP is disabled */
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
#else
|
||||
|
||||
/* Location: redirect */
|
||||
bool disallowport = FALSE;
|
||||
bool reachedmax = FALSE;
|
||||
CURLUcode uc;
|
||||
|
||||
DEBUGASSERT(type != FOLLOW_NONE);
|
||||
|
||||
if(type != FOLLOW_FAKE)
|
||||
data->state.requests++; /* count all real follows */
|
||||
if(type == FOLLOW_REDIR) {
|
||||
if((data->set.maxredirs != -1) &&
|
||||
(data->state.followlocation >= data->set.maxredirs)) {
|
||||
reachedmax = TRUE;
|
||||
type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
|
||||
to URL */
|
||||
}
|
||||
else {
|
||||
/* mark the next request as a followed location: */
|
||||
data->state.this_is_a_follow = TRUE;
|
||||
|
||||
data->state.followlocation++; /* count location-followers */
|
||||
|
||||
if(data->set.http_auto_referer) {
|
||||
CURLU *u;
|
||||
char *referer = NULL;
|
||||
|
||||
/* We are asked to automatically set the previous URL as the referer
|
||||
when we get the next URL. We pick the ->url field, which may or may
|
||||
not be 100% correct */
|
||||
|
||||
if(data->state.referer_alloc) {
|
||||
Curl_safefree(data->state.referer);
|
||||
data->state.referer_alloc = FALSE;
|
||||
}
|
||||
|
||||
/* Make a copy of the URL without crenditals and fragment */
|
||||
u = curl_url();
|
||||
if(!u)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
|
||||
|
||||
curl_url_cleanup(u);
|
||||
|
||||
if(uc || !referer)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
data->state.referer = referer;
|
||||
data->state.referer_alloc = TRUE; /* yes, free this later */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((type != FOLLOW_RETRY) &&
|
||||
(data->req.httpcode != 401) && (data->req.httpcode != 407) &&
|
||||
Curl_is_absolute_url(newurl, NULL, 0))
|
||||
/* If this is not redirect due to a 401 or 407 response and an absolute
|
||||
URL: don't allow a custom port number */
|
||||
disallowport = TRUE;
|
||||
|
||||
DEBUGASSERT(data->state.uh);
|
||||
uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl,
|
||||
(type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
|
||||
((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) |
|
||||
CURLU_ALLOW_SPACE);
|
||||
if(uc) {
|
||||
if(type != FOLLOW_FAKE)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
|
||||
/* the URL could not be parsed for some reason, but since this is FAKE
|
||||
mode, just duplicate the field as-is */
|
||||
newurl = strdup(newurl);
|
||||
if(!newurl)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
else {
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0);
|
||||
if(uc)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
|
||||
/* Clear auth if this redirects to a different port number or protocol,
|
||||
unless permitted */
|
||||
if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) {
|
||||
char *portnum;
|
||||
int port;
|
||||
bool clear = FALSE;
|
||||
|
||||
if(data->set.use_port && data->state.allow_port)
|
||||
/* a custom port is used */
|
||||
port = (int)data->set.use_port;
|
||||
else {
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum,
|
||||
CURLU_DEFAULT_PORT);
|
||||
if(uc) {
|
||||
free(newurl);
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
}
|
||||
port = atoi(portnum);
|
||||
free(portnum);
|
||||
}
|
||||
if(port != data->info.conn_remote_port) {
|
||||
infof(data, "Clear auth, redirects to port from %u to %u",
|
||||
data->info.conn_remote_port, port);
|
||||
clear = TRUE;
|
||||
}
|
||||
else {
|
||||
char *scheme;
|
||||
const struct Curl_handler *p;
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0);
|
||||
if(uc) {
|
||||
free(newurl);
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
}
|
||||
|
||||
p = Curl_builtin_scheme(scheme);
|
||||
if(p && (p->protocol != data->info.conn_protocol)) {
|
||||
infof(data, "Clear auth, redirects scheme from %s to %s",
|
||||
data->info.conn_scheme, scheme);
|
||||
clear = TRUE;
|
||||
}
|
||||
free(scheme);
|
||||
}
|
||||
if(clear) {
|
||||
Curl_safefree(data->state.aptr.user);
|
||||
Curl_safefree(data->state.aptr.passwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(type == FOLLOW_FAKE) {
|
||||
/* we're only figuring out the new url if we would've followed locations
|
||||
but now we're done so we can get out! */
|
||||
data->info.wouldredirect = newurl;
|
||||
|
||||
if(reachedmax) {
|
||||
failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs);
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(disallowport)
|
||||
data->state.allow_port = FALSE;
|
||||
|
||||
if(data->state.url_alloc)
|
||||
Curl_safefree(data->state.url);
|
||||
|
||||
data->state.url = newurl;
|
||||
data->state.url_alloc = TRUE;
|
||||
|
||||
infof(data, "Issue another request to this URL: '%s'", data->state.url);
|
||||
|
||||
/*
|
||||
* We get here when the HTTP code is 300-399 (and 401). We need to perform
|
||||
* differently based on exactly what return code there was.
|
||||
*
|
||||
* News from 7.10.6: we can also get here on a 401 or 407, in case we act on
|
||||
* a HTTP (proxy-) authentication scheme other than Basic.
|
||||
*/
|
||||
switch(data->info.httpcode) {
|
||||
/* 401 - Act on a WWW-Authenticate, we keep on moving and do the
|
||||
Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
|
||||
Proxy-Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 300 - Multiple Choices */
|
||||
/* 306 - Not used */
|
||||
/* 307 - Temporary Redirect */
|
||||
default: /* for all above (and the unknown ones) */
|
||||
/* Some codes are explicitly mentioned since I've checked RFC2616 and they
|
||||
* seem to be OK to POST to.
|
||||
*/
|
||||
break;
|
||||
case 301: /* Moved Permanently */
|
||||
/* (quote from RFC7231, section 6.4.2)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_301)) {
|
||||
infof(data, "Switch from POST to GET");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
case 302: /* Found */
|
||||
/* (quote from RFC7231, section 6.4.3)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_302)) {
|
||||
infof(data, "Switch from POST to GET");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 303: /* See Other */
|
||||
/* 'See Other' location is not the resource but a substitute for the
|
||||
* resource. In this case we switch the method to GET/HEAD, unless the
|
||||
* method is POST and the user specified to keep it as POST.
|
||||
* https://github.com/curl/curl/issues/5237#issuecomment-614641049
|
||||
*/
|
||||
if(data->state.httpreq != HTTPREQ_GET &&
|
||||
((data->state.httpreq != HTTPREQ_POST &&
|
||||
data->state.httpreq != HTTPREQ_POST_FORM &&
|
||||
data->state.httpreq != HTTPREQ_POST_MIME) ||
|
||||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
data->set.upload = false;
|
||||
infof(data, "Switch to %s",
|
||||
data->set.opt_no_body?"HEAD":"GET");
|
||||
}
|
||||
break;
|
||||
case 304: /* Not Modified */
|
||||
/* 304 means we did a conditional request and it was "Not modified".
|
||||
* We shouldn't get any Location: header in this response!
|
||||
*/
|
||||
break;
|
||||
case 305: /* Use Proxy */
|
||||
/* (quote from RFC2616, section 10.3.6):
|
||||
* "The requested resource MUST be accessed through the proxy given
|
||||
* by the Location field. The Location field gives the URI of the
|
||||
* proxy. The recipient is expected to repeat this single request
|
||||
* via the proxy. 305 responses MUST only be generated by origin
|
||||
* servers."
|
||||
*/
|
||||
break;
|
||||
}
|
||||
Curl_pgrsTime(data, TIMER_REDIRECT);
|
||||
Curl_pgrsResetTransferSizes(data);
|
||||
|
||||
return CURLE_OK;
|
||||
#endif /* CURL_DISABLE_HTTP */
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/* Provenance: curl/curl Curl_follow (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 08b8ef4e726ba10f45081ecda5b3cea788d3c839
|
||||
* parent: 08b8ef4e726ba10f45081ecda5b3cea788d3c839
|
||||
* commit_url: https://github.com/curl/curl/commit/620ea21410030a9977396b4661806bc187231b79
|
||||
* cve: CVE-2022-27774
|
||||
* license: curl
|
||||
* function: Curl_follow
|
||||
* relpath: lib/transfer.c
|
||||
*/
|
||||
|
||||
* Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string
|
||||
* as given by the remote server and set up the new URL to request.
|
||||
*
|
||||
* This function DOES NOT FREE the given url.
|
||||
*/
|
||||
CURLcode Curl_follow(struct Curl_easy *data,
|
||||
char *newurl, /* the Location: string */
|
||||
followtype type) /* see transfer.h */
|
||||
{
|
||||
#ifdef CURL_DISABLE_HTTP
|
||||
(void)data;
|
||||
(void)newurl;
|
||||
(void)type;
|
||||
/* Location: following will not happen when HTTP is disabled */
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
#else
|
||||
|
||||
/* Location: redirect */
|
||||
bool disallowport = FALSE;
|
||||
bool reachedmax = FALSE;
|
||||
CURLUcode uc;
|
||||
|
||||
DEBUGASSERT(type != FOLLOW_NONE);
|
||||
|
||||
if(type != FOLLOW_FAKE)
|
||||
data->state.requests++; /* count all real follows */
|
||||
if(type == FOLLOW_REDIR) {
|
||||
if((data->set.maxredirs != -1) &&
|
||||
(data->state.followlocation >= data->set.maxredirs)) {
|
||||
reachedmax = TRUE;
|
||||
type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected
|
||||
to URL */
|
||||
}
|
||||
else {
|
||||
/* mark the next request as a followed location: */
|
||||
data->state.this_is_a_follow = TRUE;
|
||||
|
||||
data->state.followlocation++; /* count location-followers */
|
||||
|
||||
if(data->set.http_auto_referer) {
|
||||
CURLU *u;
|
||||
char *referer = NULL;
|
||||
|
||||
/* We are asked to automatically set the previous URL as the referer
|
||||
when we get the next URL. We pick the ->url field, which may or may
|
||||
not be 100% correct */
|
||||
|
||||
if(data->state.referer_alloc) {
|
||||
Curl_safefree(data->state.referer);
|
||||
data->state.referer_alloc = FALSE;
|
||||
}
|
||||
|
||||
/* Make a copy of the URL without crenditals and fragment */
|
||||
u = curl_url();
|
||||
if(!u)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
|
||||
if(!uc)
|
||||
uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
|
||||
|
||||
curl_url_cleanup(u);
|
||||
|
||||
if(uc || !referer)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
data->state.referer = referer;
|
||||
data->state.referer_alloc = TRUE; /* yes, free this later */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((type != FOLLOW_RETRY) &&
|
||||
(data->req.httpcode != 401) && (data->req.httpcode != 407) &&
|
||||
Curl_is_absolute_url(newurl, NULL, 0))
|
||||
/* If this is not redirect due to a 401 or 407 response and an absolute
|
||||
URL: don't allow a custom port number */
|
||||
disallowport = TRUE;
|
||||
|
||||
DEBUGASSERT(data->state.uh);
|
||||
uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl,
|
||||
(type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
|
||||
((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) |
|
||||
CURLU_ALLOW_SPACE);
|
||||
if(uc) {
|
||||
if(type != FOLLOW_FAKE)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
|
||||
/* the URL could not be parsed for some reason, but since this is FAKE
|
||||
mode, just duplicate the field as-is */
|
||||
newurl = strdup(newurl);
|
||||
if(!newurl)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
else {
|
||||
|
||||
uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0);
|
||||
if(uc)
|
||||
return Curl_uc_to_curlcode(uc);
|
||||
}
|
||||
|
||||
if(type == FOLLOW_FAKE) {
|
||||
/* we're only figuring out the new url if we would've followed locations
|
||||
but now we're done so we can get out! */
|
||||
data->info.wouldredirect = newurl;
|
||||
|
||||
if(reachedmax) {
|
||||
failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs);
|
||||
return CURLE_TOO_MANY_REDIRECTS;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(disallowport)
|
||||
data->state.allow_port = FALSE;
|
||||
|
||||
if(data->state.url_alloc)
|
||||
Curl_safefree(data->state.url);
|
||||
|
||||
data->state.url = newurl;
|
||||
data->state.url_alloc = TRUE;
|
||||
|
||||
infof(data, "Issue another request to this URL: '%s'", data->state.url);
|
||||
|
||||
/*
|
||||
* We get here when the HTTP code is 300-399 (and 401). We need to perform
|
||||
* differently based on exactly what return code there was.
|
||||
*
|
||||
* News from 7.10.6: we can also get here on a 401 or 407, in case we act on
|
||||
* a HTTP (proxy-) authentication scheme other than Basic.
|
||||
*/
|
||||
switch(data->info.httpcode) {
|
||||
/* 401 - Act on a WWW-Authenticate, we keep on moving and do the
|
||||
Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 407 - Act on a Proxy-Authenticate, we keep on moving and do the
|
||||
Proxy-Authorization: XXXX header in the HTTP request code snippet */
|
||||
/* 300 - Multiple Choices */
|
||||
/* 306 - Not used */
|
||||
/* 307 - Temporary Redirect */
|
||||
default: /* for all above (and the unknown ones) */
|
||||
/* Some codes are explicitly mentioned since I've checked RFC2616 and they
|
||||
* seem to be OK to POST to.
|
||||
*/
|
||||
break;
|
||||
case 301: /* Moved Permanently */
|
||||
/* (quote from RFC7231, section 6.4.2)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_301)) {
|
||||
infof(data, "Switch from POST to GET");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
case 302: /* Found */
|
||||
/* (quote from RFC7231, section 6.4.3)
|
||||
*
|
||||
* Note: For historical reasons, a user agent MAY change the request
|
||||
* method from POST to GET for the subsequent request. If this
|
||||
* behavior is undesired, the 307 (Temporary Redirect) status code
|
||||
* can be used instead.
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* Many webservers expect this, so these servers often answers to a POST
|
||||
* request with an error page. To be sure that libcurl gets the page that
|
||||
* most user agents would get, libcurl has to force GET.
|
||||
*
|
||||
* This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
|
||||
* can be overridden with CURLOPT_POSTREDIR.
|
||||
*/
|
||||
if((data->state.httpreq == HTTPREQ_POST
|
||||
|| data->state.httpreq == HTTPREQ_POST_FORM
|
||||
|| data->state.httpreq == HTTPREQ_POST_MIME)
|
||||
&& !(data->set.keep_post & CURL_REDIR_POST_302)) {
|
||||
infof(data, "Switch from POST to GET");
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 303: /* See Other */
|
||||
/* 'See Other' location is not the resource but a substitute for the
|
||||
* resource. In this case we switch the method to GET/HEAD, unless the
|
||||
* method is POST and the user specified to keep it as POST.
|
||||
* https://github.com/curl/curl/issues/5237#issuecomment-614641049
|
||||
*/
|
||||
if(data->state.httpreq != HTTPREQ_GET &&
|
||||
((data->state.httpreq != HTTPREQ_POST &&
|
||||
data->state.httpreq != HTTPREQ_POST_FORM &&
|
||||
data->state.httpreq != HTTPREQ_POST_MIME) ||
|
||||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
|
||||
data->state.httpreq = HTTPREQ_GET;
|
||||
data->set.upload = false;
|
||||
infof(data, "Switch to %s",
|
||||
data->set.opt_no_body?"HEAD":"GET");
|
||||
}
|
||||
break;
|
||||
case 304: /* Not Modified */
|
||||
/* 304 means we did a conditional request and it was "Not modified".
|
||||
* We shouldn't get any Location: header in this response!
|
||||
*/
|
||||
break;
|
||||
case 305: /* Use Proxy */
|
||||
/* (quote from RFC2616, section 10.3.6):
|
||||
* "The requested resource MUST be accessed through the proxy given
|
||||
* by the Location field. The Location field gives the URI of the
|
||||
* proxy. The recipient is expected to repeat this single request
|
||||
* via the proxy. 305 responses MUST only be generated by origin
|
||||
* servers."
|
||||
*/
|
||||
break;
|
||||
}
|
||||
Curl_pgrsTime(data, TIMER_REDIRECT);
|
||||
Curl_pgrsResetTransferSizes(data);
|
||||
|
||||
return CURLE_OK;
|
||||
#endif /* CURL_DISABLE_HTTP */
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Provenance: curl/curl hashkey (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 058f98dc3fe595f21dc26a5b9b1699e519ba5705
|
||||
* parent: 852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* commit_url: https://github.com/curl/curl/commit/058f98dc3fe595f21dc26a5b9b1699e519ba5705
|
||||
* cve: CVE-2022-27775
|
||||
* license: curl
|
||||
* function: hashkey
|
||||
* relpath: lib/conncache.c
|
||||
*/
|
||||
|
||||
static void hashkey(struct connectdata *conn, char *buf,
|
||||
size_t len, /* something like 128 is fine */
|
||||
const char **hostp)
|
||||
{
|
||||
const char *hostname;
|
||||
long port = conn->remote_port;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
|
||||
hostname = conn->http_proxy.host.name;
|
||||
port = conn->port;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(conn->bits.conn_to_host)
|
||||
hostname = conn->conn_to_host.name;
|
||||
else
|
||||
hostname = conn->host.name;
|
||||
|
||||
if(hostp)
|
||||
/* report back which name we used */
|
||||
*hostp = hostname;
|
||||
|
||||
/* put the numbers first so that the hostname gets cut off if too long */
|
||||
#ifdef ENABLE_IPV6
|
||||
msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname);
|
||||
#else
|
||||
msnprintf(buf, len, "%ld/%s", port, hostname);
|
||||
#endif
|
||||
Curl_strntolower(buf, buf, len);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* Provenance: curl/curl hashkey (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* parent: 852aa5ad351ea53e5f01d2f44b5b4370c2bf5425
|
||||
* commit_url: https://github.com/curl/curl/commit/058f98dc3fe595f21dc26a5b9b1699e519ba5705
|
||||
* cve: CVE-2022-27775
|
||||
* license: curl
|
||||
* function: hashkey
|
||||
* relpath: lib/conncache.c
|
||||
*/
|
||||
|
||||
static void hashkey(struct connectdata *conn, char *buf,
|
||||
size_t len, /* something like 128 is fine */
|
||||
const char **hostp)
|
||||
{
|
||||
const char *hostname;
|
||||
long port = conn->remote_port;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
|
||||
hostname = conn->http_proxy.host.name;
|
||||
port = conn->port;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(conn->bits.conn_to_host)
|
||||
hostname = conn->conn_to_host.name;
|
||||
else
|
||||
hostname = conn->host.name;
|
||||
|
||||
if(hostp)
|
||||
/* report back which name we used */
|
||||
*hostp = hostname;
|
||||
|
||||
/* put the number first so that the hostname gets cut off if too long */
|
||||
msnprintf(buf, len, "%ld%s", port, hostname);
|
||||
Curl_strntolower(buf, buf, len);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/* Provenance: curl/curl Curl_add_custom_headers (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 6e659993952aa5f90f48864be84a1bbb047fc258
|
||||
* parent: 8f2079154f80561ccffcaa954268798b658e66b6
|
||||
* commit_url: https://github.com/curl/curl/commit/6e659993952aa5f90f48864be84a1bbb047fc258
|
||||
* cve: CVE-2022-27776
|
||||
* license: curl
|
||||
* function: Curl_add_custom_headers
|
||||
* relpath: lib/http.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
|
||||
bool is_connect,
|
||||
#ifndef USE_HYPER
|
||||
struct dynbuf *req
|
||||
#else
|
||||
void *req
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct connectdata *conn = data->conn;
|
||||
char *ptr;
|
||||
struct curl_slist *h[2];
|
||||
struct curl_slist *headers;
|
||||
int numlists = 1; /* by default */
|
||||
int i;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
enum proxy_use proxy;
|
||||
|
||||
if(is_connect)
|
||||
proxy = HEADER_CONNECT;
|
||||
else
|
||||
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy?
|
||||
HEADER_PROXY:HEADER_SERVER;
|
||||
|
||||
switch(proxy) {
|
||||
case HEADER_SERVER:
|
||||
h[0] = data->set.headers;
|
||||
break;
|
||||
case HEADER_PROXY:
|
||||
h[0] = data->set.headers;
|
||||
if(data->set.sep_headers) {
|
||||
h[1] = data->set.proxyheaders;
|
||||
numlists++;
|
||||
}
|
||||
break;
|
||||
case HEADER_CONNECT:
|
||||
if(data->set.sep_headers)
|
||||
h[0] = data->set.proxyheaders;
|
||||
else
|
||||
h[0] = data->set.headers;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
(void)is_connect;
|
||||
h[0] = data->set.headers;
|
||||
#endif
|
||||
|
||||
/* loop through one or two lists */
|
||||
for(i = 0; i < numlists; i++) {
|
||||
headers = h[i];
|
||||
|
||||
while(headers) {
|
||||
char *semicolonp = NULL;
|
||||
ptr = strchr(headers->data, ':');
|
||||
if(!ptr) {
|
||||
char *optr;
|
||||
/* no colon, semicolon? */
|
||||
ptr = strchr(headers->data, ';');
|
||||
if(ptr) {
|
||||
optr = ptr;
|
||||
ptr++; /* pass the semicolon */
|
||||
while(*ptr && ISSPACE(*ptr))
|
||||
ptr++;
|
||||
|
||||
if(*ptr) {
|
||||
/* this may be used for something else in the future */
|
||||
optr = NULL;
|
||||
}
|
||||
else {
|
||||
if(*(--ptr) == ';') {
|
||||
/* copy the source */
|
||||
semicolonp = strdup(headers->data);
|
||||
if(!semicolonp) {
|
||||
#ifndef USE_HYPER
|
||||
Curl_dyn_free(req);
|
||||
#endif
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
/* put a colon where the semicolon is */
|
||||
semicolonp[ptr - headers->data] = ':';
|
||||
/* point at the colon */
|
||||
optr = &semicolonp [ptr - headers->data];
|
||||
}
|
||||
}
|
||||
ptr = optr;
|
||||
}
|
||||
}
|
||||
if(ptr && (ptr != headers->data)) {
|
||||
/* we require a colon for this to be a true header */
|
||||
|
||||
ptr++; /* pass the colon */
|
||||
while(*ptr && ISSPACE(*ptr))
|
||||
ptr++;
|
||||
|
||||
if(*ptr || semicolonp) {
|
||||
/* only send this if the contents was non-blank or done special */
|
||||
CURLcode result = CURLE_OK;
|
||||
char *compare = semicolonp ? semicolonp : headers->data;
|
||||
|
||||
if(data->state.aptr.host &&
|
||||
/* a Host: header was sent already, don't pass on any custom Host:
|
||||
header as that will produce *two* in the same request! */
|
||||
checkprefix("Host:", compare))
|
||||
;
|
||||
else if(data->state.httpreq == HTTPREQ_POST_FORM &&
|
||||
/* this header (extended by formdata.c) is sent later */
|
||||
checkprefix("Content-Type:", compare))
|
||||
;
|
||||
else if(data->state.httpreq == HTTPREQ_POST_MIME &&
|
||||
/* this header is sent later */
|
||||
checkprefix("Content-Type:", compare))
|
||||
;
|
||||
else if(conn->bits.authneg &&
|
||||
/* while doing auth neg, don't allow the custom length since
|
||||
we will force length zero then */
|
||||
checkprefix("Content-Length:", compare))
|
||||
;
|
||||
else if(data->state.aptr.te &&
|
||||
/* when asking for Transfer-Encoding, don't pass on a custom
|
||||
Connection: */
|
||||
checkprefix("Connection:", compare))
|
||||
;
|
||||
else if((conn->httpversion >= 20) &&
|
||||
checkprefix("Transfer-Encoding:", compare))
|
||||
/* HTTP/2 doesn't support chunked requests */
|
||||
;
|
||||
else if((checkprefix("Authorization:", compare) ||
|
||||
checkprefix("Cookie:", compare)) &&
|
||||
/* be careful of sending this potentially sensitive header to
|
||||
other hosts */
|
||||
!allow_auth_to_host(data))
|
||||
;
|
||||
else {
|
||||
#ifdef USE_HYPER
|
||||
result = Curl_hyper_header(data, req, compare);
|
||||
#else
|
||||
result = Curl_dyn_addf(req, "%s\r\n", compare);
|
||||
#endif
|
||||
}
|
||||
if(semicolonp)
|
||||
free(semicolonp);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
headers = headers->next;
|
||||
}
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/* Provenance: curl/curl Curl_add_custom_headers (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 8f2079154f80561ccffcaa954268798b658e66b6
|
||||
* parent: 8f2079154f80561ccffcaa954268798b658e66b6
|
||||
* commit_url: https://github.com/curl/curl/commit/6e659993952aa5f90f48864be84a1bbb047fc258
|
||||
* cve: CVE-2022-27776
|
||||
* license: curl
|
||||
* function: Curl_add_custom_headers
|
||||
* relpath: lib/http.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
|
||||
bool is_connect,
|
||||
#ifndef USE_HYPER
|
||||
struct dynbuf *req
|
||||
#else
|
||||
void *req
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct connectdata *conn = data->conn;
|
||||
char *ptr;
|
||||
struct curl_slist *h[2];
|
||||
struct curl_slist *headers;
|
||||
int numlists = 1; /* by default */
|
||||
int i;
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
enum proxy_use proxy;
|
||||
|
||||
if(is_connect)
|
||||
proxy = HEADER_CONNECT;
|
||||
else
|
||||
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy?
|
||||
HEADER_PROXY:HEADER_SERVER;
|
||||
|
||||
switch(proxy) {
|
||||
case HEADER_SERVER:
|
||||
h[0] = data->set.headers;
|
||||
break;
|
||||
case HEADER_PROXY:
|
||||
h[0] = data->set.headers;
|
||||
if(data->set.sep_headers) {
|
||||
h[1] = data->set.proxyheaders;
|
||||
numlists++;
|
||||
}
|
||||
break;
|
||||
case HEADER_CONNECT:
|
||||
if(data->set.sep_headers)
|
||||
h[0] = data->set.proxyheaders;
|
||||
else
|
||||
h[0] = data->set.headers;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
(void)is_connect;
|
||||
h[0] = data->set.headers;
|
||||
#endif
|
||||
|
||||
/* loop through one or two lists */
|
||||
for(i = 0; i < numlists; i++) {
|
||||
headers = h[i];
|
||||
|
||||
while(headers) {
|
||||
char *semicolonp = NULL;
|
||||
ptr = strchr(headers->data, ':');
|
||||
if(!ptr) {
|
||||
char *optr;
|
||||
/* no colon, semicolon? */
|
||||
ptr = strchr(headers->data, ';');
|
||||
if(ptr) {
|
||||
optr = ptr;
|
||||
ptr++; /* pass the semicolon */
|
||||
while(*ptr && ISSPACE(*ptr))
|
||||
ptr++;
|
||||
|
||||
if(*ptr) {
|
||||
/* this may be used for something else in the future */
|
||||
optr = NULL;
|
||||
}
|
||||
else {
|
||||
if(*(--ptr) == ';') {
|
||||
/* copy the source */
|
||||
semicolonp = strdup(headers->data);
|
||||
if(!semicolonp) {
|
||||
#ifndef USE_HYPER
|
||||
Curl_dyn_free(req);
|
||||
#endif
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
/* put a colon where the semicolon is */
|
||||
semicolonp[ptr - headers->data] = ':';
|
||||
/* point at the colon */
|
||||
optr = &semicolonp [ptr - headers->data];
|
||||
}
|
||||
}
|
||||
ptr = optr;
|
||||
}
|
||||
}
|
||||
if(ptr && (ptr != headers->data)) {
|
||||
/* we require a colon for this to be a true header */
|
||||
|
||||
ptr++; /* pass the colon */
|
||||
while(*ptr && ISSPACE(*ptr))
|
||||
ptr++;
|
||||
|
||||
if(*ptr || semicolonp) {
|
||||
/* only send this if the contents was non-blank or done special */
|
||||
CURLcode result = CURLE_OK;
|
||||
char *compare = semicolonp ? semicolonp : headers->data;
|
||||
|
||||
if(data->state.aptr.host &&
|
||||
/* a Host: header was sent already, don't pass on any custom Host:
|
||||
header as that will produce *two* in the same request! */
|
||||
checkprefix("Host:", compare))
|
||||
;
|
||||
else if(data->state.httpreq == HTTPREQ_POST_FORM &&
|
||||
/* this header (extended by formdata.c) is sent later */
|
||||
checkprefix("Content-Type:", compare))
|
||||
;
|
||||
else if(data->state.httpreq == HTTPREQ_POST_MIME &&
|
||||
/* this header is sent later */
|
||||
checkprefix("Content-Type:", compare))
|
||||
;
|
||||
else if(conn->bits.authneg &&
|
||||
/* while doing auth neg, don't allow the custom length since
|
||||
we will force length zero then */
|
||||
checkprefix("Content-Length:", compare))
|
||||
;
|
||||
else if(data->state.aptr.te &&
|
||||
/* when asking for Transfer-Encoding, don't pass on a custom
|
||||
Connection: */
|
||||
checkprefix("Connection:", compare))
|
||||
;
|
||||
else if((conn->httpversion >= 20) &&
|
||||
checkprefix("Transfer-Encoding:", compare))
|
||||
/* HTTP/2 doesn't support chunked requests */
|
||||
;
|
||||
else if((checkprefix("Authorization:", compare) ||
|
||||
checkprefix("Cookie:", compare)) &&
|
||||
/* be careful of sending this potentially sensitive header to
|
||||
other hosts */
|
||||
(data->state.this_is_a_follow &&
|
||||
data->state.first_host &&
|
||||
!data->set.allow_auth_to_other_hosts &&
|
||||
!strcasecompare(data->state.first_host, conn->host.name)))
|
||||
;
|
||||
else {
|
||||
#ifdef USE_HYPER
|
||||
result = Curl_hyper_header(data, req, compare);
|
||||
#else
|
||||
result = Curl_dyn_addf(req, "%s\r\n", compare);
|
||||
#endif
|
||||
}
|
||||
if(semicolonp)
|
||||
free(semicolonp);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
headers = headers->next;
|
||||
}
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/* Provenance: curl/curl post_per_transfer (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 8c7ee9083d0d719d0a77ab20d9cc2ae84eeea7f3
|
||||
* parent: fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* commit_url: https://github.com/curl/curl/commit/8c7ee9083d0d719d0a77ab20d9cc2ae84eeea7f3
|
||||
* cve: CVE-2022-27778
|
||||
* license: curl
|
||||
* function: post_per_transfer
|
||||
* relpath: src/tool_operate.c
|
||||
*/
|
||||
|
||||
static CURLcode post_per_transfer(struct GlobalConfig *global,
|
||||
struct per_transfer *per,
|
||||
CURLcode result,
|
||||
bool *retryp,
|
||||
long *delay) /* milliseconds! */
|
||||
{
|
||||
struct OutStruct *outs = &per->outs;
|
||||
CURL *curl = per->curl;
|
||||
struct OperationConfig *config = per->config;
|
||||
|
||||
if(!curl || !config)
|
||||
return result;
|
||||
|
||||
*retryp = FALSE;
|
||||
*delay = 0; /* for no retry, keep it zero */
|
||||
|
||||
if(per->infdopen)
|
||||
close(per->infd);
|
||||
|
||||
#ifdef __VMS
|
||||
if(is_vms_shell()) {
|
||||
/* VMS DCL shell behavior */
|
||||
if(!global->showerror)
|
||||
vms_show = VMSSTS_HIDE;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!config->synthetic_error && result && global->showerror) {
|
||||
fprintf(global->errors, "curl: (%d) %s\n", result,
|
||||
(per->errorbuffer[0]) ? per->errorbuffer :
|
||||
curl_easy_strerror(result));
|
||||
if(result == CURLE_PEER_FAILED_VERIFICATION)
|
||||
fputs(CURL_CA_CERT_ERRORMSG, global->errors);
|
||||
}
|
||||
else if(config->failwithbody) {
|
||||
/* if HTTP response >= 400, return error */
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
if(code >= 400) {
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (%d) The requested URL returned error: %ld\n",
|
||||
CURLE_HTTP_RETURNED_ERROR, code);
|
||||
result = CURLE_HTTP_RETURNED_ERROR;
|
||||
}
|
||||
}
|
||||
/* Set file extended attributes */
|
||||
if(!result && config->xattr && outs->fopened && outs->stream) {
|
||||
int rc = fwrite_xattr(curl, fileno(outs->stream));
|
||||
if(rc)
|
||||
warnf(config->global, "Error setting extended attributes on '%s': %s\n",
|
||||
outs->filename, strerror(errno));
|
||||
}
|
||||
|
||||
if(!result && !outs->stream && !outs->bytes) {
|
||||
/* we have received no data despite the transfer was successful
|
||||
==> force creation of an empty output file (if an output file
|
||||
was specified) */
|
||||
long cond_unmet = 0L;
|
||||
/* do not create (or even overwrite) the file in case we get no
|
||||
data because of unmet condition */
|
||||
curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &cond_unmet);
|
||||
if(!cond_unmet && !tool_create_output_file(outs, config))
|
||||
result = CURLE_WRITE_ERROR;
|
||||
}
|
||||
|
||||
if(!outs->s_isreg && outs->stream) {
|
||||
/* Dump standard stream buffered data */
|
||||
int rc = fflush(outs->stream);
|
||||
if(!result && rc) {
|
||||
/* something went wrong in the writing process */
|
||||
result = CURLE_WRITE_ERROR;
|
||||
if(global->showerror)
|
||||
fprintf(global->errors, "curl: (%d) Failed writing body\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
/* if retry-max-time is non-zero, make sure we haven't exceeded the
|
||||
time */
|
||||
if(per->retry_numretries &&
|
||||
(!config->retry_maxtime ||
|
||||
(tvdiff(tvnow(), per->retrystart) <
|
||||
config->retry_maxtime*1000L)) ) {
|
||||
enum {
|
||||
RETRY_NO,
|
||||
RETRY_ALL_ERRORS,
|
||||
RETRY_TIMEOUT,
|
||||
RETRY_CONNREFUSED,
|
||||
RETRY_HTTP,
|
||||
RETRY_FTP,
|
||||
RETRY_LAST /* not used */
|
||||
} retry = RETRY_NO;
|
||||
long response = 0;
|
||||
if((CURLE_OPERATION_TIMEDOUT == result) ||
|
||||
(CURLE_COULDNT_RESOLVE_HOST == result) ||
|
||||
(CURLE_COULDNT_RESOLVE_PROXY == result) ||
|
||||
(CURLE_FTP_ACCEPT_TIMEOUT == result))
|
||||
/* retry timeout always */
|
||||
retry = RETRY_TIMEOUT;
|
||||
else if(config->retry_connrefused &&
|
||||
(CURLE_COULDNT_CONNECT == result)) {
|
||||
long oserrno = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno);
|
||||
if(ECONNREFUSED == oserrno)
|
||||
retry = RETRY_CONNREFUSED;
|
||||
}
|
||||
else if((CURLE_OK == result) ||
|
||||
(config->failonerror &&
|
||||
(CURLE_HTTP_RETURNED_ERROR == result))) {
|
||||
/* If it returned OK. _or_ failonerror was enabled and it
|
||||
returned due to such an error, check for HTTP transient
|
||||
errors to retry on. */
|
||||
long protocol = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol);
|
||||
if((protocol == CURLPROTO_HTTP) || (protocol == CURLPROTO_HTTPS)) {
|
||||
/* This was HTTP(S) */
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
|
||||
|
||||
switch(response) {
|
||||
case 408: /* Request Timeout */
|
||||
case 429: /* Too Many Requests (RFC6585) */
|
||||
case 500: /* Internal Server Error */
|
||||
case 502: /* Bad Gateway */
|
||||
case 503: /* Service Unavailable */
|
||||
case 504: /* Gateway Timeout */
|
||||
retry = RETRY_HTTP;
|
||||
/*
|
||||
* At this point, we have already written data to the output
|
||||
* file (or terminal). If we write to a file, we must rewind
|
||||
* or close/re-open the file so that the next attempt starts
|
||||
* over from the beginning.
|
||||
*
|
||||
* TODO: similar action for the upload case. We might need
|
||||
* to start over reading from a previous point if we have
|
||||
* uploaded something when this was returned.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
} /* if CURLE_OK */
|
||||
else if(result) {
|
||||
long protocol = 0;
|
||||
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
|
||||
curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol);
|
||||
|
||||
if((protocol == CURLPROTO_FTP || protocol == CURLPROTO_FTPS) &&
|
||||
response / 100 == 4)
|
||||
/*
|
||||
* This is typically when the FTP server only allows a certain
|
||||
* amount of users and we are not one of them. All 4xx codes
|
||||
* are transient.
|
||||
*/
|
||||
retry = RETRY_FTP;
|
||||
}
|
||||
|
||||
if(result && !retry && config->retry_all_errors)
|
||||
retry = RETRY_ALL_ERRORS;
|
||||
|
||||
if(retry) {
|
||||
long sleeptime = 0;
|
||||
curl_off_t retry_after = 0;
|
||||
static const char * const m[]={
|
||||
NULL,
|
||||
"(retrying all errors)",
|
||||
": timeout",
|
||||
": connection refused",
|
||||
": HTTP error",
|
||||
": FTP error"
|
||||
};
|
||||
|
||||
sleeptime = per->retry_sleep;
|
||||
if(RETRY_HTTP == retry) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
|
||||
if(retry_after) {
|
||||
/* store in a 'long', make sure it doesn't overflow */
|
||||
if(retry_after > LONG_MAX/1000)
|
||||
sleeptime = LONG_MAX;
|
||||
else
|
||||
sleeptime = (long)retry_after * 1000; /* milliseconds */
|
||||
|
||||
/* if adding retry_after seconds to the process would exceed the
|
||||
maximum time allowed for retrying, then exit the retries right
|
||||
away */
|
||||
if(config->retry_maxtime) {
|
||||
curl_off_t seconds = tvdiff(tvnow(), per->retrystart)/1000;
|
||||
|
||||
if((CURL_OFF_T_MAX - retry_after < seconds) ||
|
||||
(seconds + retry_after > config->retry_maxtime)) {
|
||||
warnf(config->global, "The Retry-After: time would "
|
||||
"make this command line exceed the maximum allowed time "
|
||||
"for retries.");
|
||||
goto noretry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
warnf(config->global, "Problem %s. "
|
||||
"Will retry in %ld seconds. "
|
||||
"%ld retries left.\n",
|
||||
m[retry], sleeptime/1000L, per->retry_numretries);
|
||||
|
||||
per->retry_numretries--;
|
||||
if(!config->retry_delay) {
|
||||
per->retry_sleep *= 2;
|
||||
if(per->retry_sleep > RETRY_SLEEP_MAX)
|
||||
per->retry_sleep = RETRY_SLEEP_MAX;
|
||||
}
|
||||
if(outs->bytes && outs->filename && outs->stream) {
|
||||
int rc;
|
||||
/* We have written data to an output file, we truncate file
|
||||
*/
|
||||
if(!global->mute)
|
||||
fprintf(global->errors, "Throwing away %"
|
||||
CURL_FORMAT_CURL_OFF_T " bytes\n",
|
||||
outs->bytes);
|
||||
fflush(outs->stream);
|
||||
/* truncate file at the position where we started appending */
|
||||
#ifdef HAVE_FTRUNCATE
|
||||
if(ftruncate(fileno(outs->stream), outs->init)) {
|
||||
/* when truncate fails, we can't just append as then we'll
|
||||
create something strange, bail out */
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (23) Failed to truncate file\n");
|
||||
return CURLE_WRITE_ERROR;
|
||||
}
|
||||
/* now seek to the end of the file, the position where we
|
||||
just truncated the file in a large file-safe way */
|
||||
rc = fseek(outs->stream, 0, SEEK_END);
|
||||
#else
|
||||
/* ftruncate is not available, so just reposition the file
|
||||
to the location we would have truncated it. This won't
|
||||
work properly with large files on 32-bit systems, but
|
||||
most of those will have ftruncate. */
|
||||
rc = fseek(outs->stream, (long)outs->init, SEEK_SET);
|
||||
#endif
|
||||
if(rc) {
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (23) Failed seeking to end of file\n");
|
||||
return CURLE_WRITE_ERROR;
|
||||
}
|
||||
outs->bytes = 0; /* clear for next round */
|
||||
}
|
||||
*retryp = TRUE;
|
||||
*delay = sleeptime;
|
||||
return CURLE_OK;
|
||||
}
|
||||
} /* if retry_numretries */
|
||||
noretry:
|
||||
|
||||
if((global->progressmode == CURL_PROGRESS_BAR) &&
|
||||
per->progressbar.calls)
|
||||
/* if the custom progress bar has been displayed, we output a
|
||||
newline here */
|
||||
fputs("\n", per->progressbar.out);
|
||||
|
||||
/* Close the outs file */
|
||||
if(outs->fopened && outs->stream) {
|
||||
int rc = fclose(outs->stream);
|
||||
if(!result && rc) {
|
||||
/* something went wrong in the writing process */
|
||||
result = CURLE_WRITE_ERROR;
|
||||
if(global->showerror)
|
||||
fprintf(global->errors, "curl: (%d) Failed writing body\n", result);
|
||||
}
|
||||
if(result && config->rm_partial) {
|
||||
notef(global, "Removing output file: %s\n", outs->filename);
|
||||
unlink(outs->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* File time can only be set _after_ the file has been closed */
|
||||
if(!result && config->remote_time && outs->s_isreg && outs->filename) {
|
||||
/* Ask libcurl if we got a remote file time */
|
||||
curl_off_t filetime = -1;
|
||||
curl_easy_getinfo(curl, CURLINFO_FILETIME_T, &filetime);
|
||||
setfiletime(filetime, outs->filename, global);
|
||||
}
|
||||
|
||||
/* Write the --write-out data before cleanup but after result is final */
|
||||
if(config->writeout)
|
||||
ourWriteOut(config->writeout, per, result);
|
||||
|
||||
/* Close function-local opened file descriptors */
|
||||
if(per->heads.fopened && per->heads.stream)
|
||||
fclose(per->heads.stream);
|
||||
|
||||
if(per->heads.alloc_filename)
|
||||
Curl_safefree(per->heads.filename);
|
||||
|
||||
if(per->etag_save.fopened && per->etag_save.stream)
|
||||
fclose(per->etag_save.stream);
|
||||
|
||||
if(per->etag_save.alloc_filename)
|
||||
Curl_safefree(per->etag_save.filename);
|
||||
|
||||
curl_easy_cleanup(per->curl);
|
||||
if(outs->alloc_filename)
|
||||
free(outs->filename);
|
||||
free(per->this_url);
|
||||
free(per->outfile);
|
||||
free(per->uploadfile);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/* Provenance: curl/curl post_per_transfer (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* parent: fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* commit_url: https://github.com/curl/curl/commit/8c7ee9083d0d719d0a77ab20d9cc2ae84eeea7f3
|
||||
* cve: CVE-2022-27778
|
||||
* license: curl
|
||||
* function: post_per_transfer
|
||||
* relpath: src/tool_operate.c
|
||||
*/
|
||||
|
||||
static CURLcode post_per_transfer(struct GlobalConfig *global,
|
||||
struct per_transfer *per,
|
||||
CURLcode result,
|
||||
bool *retryp,
|
||||
long *delay) /* milliseconds! */
|
||||
{
|
||||
struct OutStruct *outs = &per->outs;
|
||||
CURL *curl = per->curl;
|
||||
struct OperationConfig *config = per->config;
|
||||
|
||||
if(!curl || !config)
|
||||
return result;
|
||||
|
||||
*retryp = FALSE;
|
||||
*delay = 0; /* for no retry, keep it zero */
|
||||
|
||||
if(per->infdopen)
|
||||
close(per->infd);
|
||||
|
||||
#ifdef __VMS
|
||||
if(is_vms_shell()) {
|
||||
/* VMS DCL shell behavior */
|
||||
if(!global->showerror)
|
||||
vms_show = VMSSTS_HIDE;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!config->synthetic_error && result && global->showerror) {
|
||||
fprintf(global->errors, "curl: (%d) %s\n", result,
|
||||
(per->errorbuffer[0]) ? per->errorbuffer :
|
||||
curl_easy_strerror(result));
|
||||
if(result == CURLE_PEER_FAILED_VERIFICATION)
|
||||
fputs(CURL_CA_CERT_ERRORMSG, global->errors);
|
||||
}
|
||||
else if(config->failwithbody) {
|
||||
/* if HTTP response >= 400, return error */
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
if(code >= 400) {
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (%d) The requested URL returned error: %ld\n",
|
||||
CURLE_HTTP_RETURNED_ERROR, code);
|
||||
result = CURLE_HTTP_RETURNED_ERROR;
|
||||
}
|
||||
}
|
||||
/* Set file extended attributes */
|
||||
if(!result && config->xattr && outs->fopened && outs->stream) {
|
||||
int rc = fwrite_xattr(curl, fileno(outs->stream));
|
||||
if(rc)
|
||||
warnf(config->global, "Error setting extended attributes on '%s': %s\n",
|
||||
outs->filename, strerror(errno));
|
||||
}
|
||||
|
||||
if(!result && !outs->stream && !outs->bytes) {
|
||||
/* we have received no data despite the transfer was successful
|
||||
==> force creation of an empty output file (if an output file
|
||||
was specified) */
|
||||
long cond_unmet = 0L;
|
||||
/* do not create (or even overwrite) the file in case we get no
|
||||
data because of unmet condition */
|
||||
curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &cond_unmet);
|
||||
if(!cond_unmet && !tool_create_output_file(outs, config))
|
||||
result = CURLE_WRITE_ERROR;
|
||||
}
|
||||
|
||||
if(!outs->s_isreg && outs->stream) {
|
||||
/* Dump standard stream buffered data */
|
||||
int rc = fflush(outs->stream);
|
||||
if(!result && rc) {
|
||||
/* something went wrong in the writing process */
|
||||
result = CURLE_WRITE_ERROR;
|
||||
if(global->showerror)
|
||||
fprintf(global->errors, "curl: (%d) Failed writing body\n", result);
|
||||
}
|
||||
}
|
||||
|
||||
/* if retry-max-time is non-zero, make sure we haven't exceeded the
|
||||
time */
|
||||
if(per->retry_numretries &&
|
||||
(!config->retry_maxtime ||
|
||||
(tvdiff(tvnow(), per->retrystart) <
|
||||
config->retry_maxtime*1000L)) ) {
|
||||
enum {
|
||||
RETRY_NO,
|
||||
RETRY_ALL_ERRORS,
|
||||
RETRY_TIMEOUT,
|
||||
RETRY_CONNREFUSED,
|
||||
RETRY_HTTP,
|
||||
RETRY_FTP,
|
||||
RETRY_LAST /* not used */
|
||||
} retry = RETRY_NO;
|
||||
long response = 0;
|
||||
if((CURLE_OPERATION_TIMEDOUT == result) ||
|
||||
(CURLE_COULDNT_RESOLVE_HOST == result) ||
|
||||
(CURLE_COULDNT_RESOLVE_PROXY == result) ||
|
||||
(CURLE_FTP_ACCEPT_TIMEOUT == result))
|
||||
/* retry timeout always */
|
||||
retry = RETRY_TIMEOUT;
|
||||
else if(config->retry_connrefused &&
|
||||
(CURLE_COULDNT_CONNECT == result)) {
|
||||
long oserrno = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno);
|
||||
if(ECONNREFUSED == oserrno)
|
||||
retry = RETRY_CONNREFUSED;
|
||||
}
|
||||
else if((CURLE_OK == result) ||
|
||||
(config->failonerror &&
|
||||
(CURLE_HTTP_RETURNED_ERROR == result))) {
|
||||
/* If it returned OK. _or_ failonerror was enabled and it
|
||||
returned due to such an error, check for HTTP transient
|
||||
errors to retry on. */
|
||||
long protocol = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol);
|
||||
if((protocol == CURLPROTO_HTTP) || (protocol == CURLPROTO_HTTPS)) {
|
||||
/* This was HTTP(S) */
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
|
||||
|
||||
switch(response) {
|
||||
case 408: /* Request Timeout */
|
||||
case 429: /* Too Many Requests (RFC6585) */
|
||||
case 500: /* Internal Server Error */
|
||||
case 502: /* Bad Gateway */
|
||||
case 503: /* Service Unavailable */
|
||||
case 504: /* Gateway Timeout */
|
||||
retry = RETRY_HTTP;
|
||||
/*
|
||||
* At this point, we have already written data to the output
|
||||
* file (or terminal). If we write to a file, we must rewind
|
||||
* or close/re-open the file so that the next attempt starts
|
||||
* over from the beginning.
|
||||
*
|
||||
* TODO: similar action for the upload case. We might need
|
||||
* to start over reading from a previous point if we have
|
||||
* uploaded something when this was returned.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
} /* if CURLE_OK */
|
||||
else if(result) {
|
||||
long protocol = 0;
|
||||
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
|
||||
curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol);
|
||||
|
||||
if((protocol == CURLPROTO_FTP || protocol == CURLPROTO_FTPS) &&
|
||||
response / 100 == 4)
|
||||
/*
|
||||
* This is typically when the FTP server only allows a certain
|
||||
* amount of users and we are not one of them. All 4xx codes
|
||||
* are transient.
|
||||
*/
|
||||
retry = RETRY_FTP;
|
||||
}
|
||||
|
||||
if(result && !retry && config->retry_all_errors)
|
||||
retry = RETRY_ALL_ERRORS;
|
||||
|
||||
if(retry) {
|
||||
long sleeptime = 0;
|
||||
curl_off_t retry_after = 0;
|
||||
static const char * const m[]={
|
||||
NULL,
|
||||
"(retrying all errors)",
|
||||
": timeout",
|
||||
": connection refused",
|
||||
": HTTP error",
|
||||
": FTP error"
|
||||
};
|
||||
|
||||
sleeptime = per->retry_sleep;
|
||||
if(RETRY_HTTP == retry) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
|
||||
if(retry_after) {
|
||||
/* store in a 'long', make sure it doesn't overflow */
|
||||
if(retry_after > LONG_MAX/1000)
|
||||
sleeptime = LONG_MAX;
|
||||
else
|
||||
sleeptime = (long)retry_after * 1000; /* milliseconds */
|
||||
|
||||
/* if adding retry_after seconds to the process would exceed the
|
||||
maximum time allowed for retrying, then exit the retries right
|
||||
away */
|
||||
if(config->retry_maxtime) {
|
||||
curl_off_t seconds = tvdiff(tvnow(), per->retrystart)/1000;
|
||||
|
||||
if((CURL_OFF_T_MAX - retry_after < seconds) ||
|
||||
(seconds + retry_after > config->retry_maxtime)) {
|
||||
warnf(config->global, "The Retry-After: time would "
|
||||
"make this command line exceed the maximum allowed time "
|
||||
"for retries.");
|
||||
goto noretry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
warnf(config->global, "Problem %s. "
|
||||
"Will retry in %ld seconds. "
|
||||
"%ld retries left.\n",
|
||||
m[retry], sleeptime/1000L, per->retry_numretries);
|
||||
|
||||
per->retry_numretries--;
|
||||
if(!config->retry_delay) {
|
||||
per->retry_sleep *= 2;
|
||||
if(per->retry_sleep > RETRY_SLEEP_MAX)
|
||||
per->retry_sleep = RETRY_SLEEP_MAX;
|
||||
}
|
||||
if(outs->bytes && outs->filename && outs->stream) {
|
||||
int rc;
|
||||
/* We have written data to an output file, we truncate file
|
||||
*/
|
||||
if(!global->mute)
|
||||
fprintf(global->errors, "Throwing away %"
|
||||
CURL_FORMAT_CURL_OFF_T " bytes\n",
|
||||
outs->bytes);
|
||||
fflush(outs->stream);
|
||||
/* truncate file at the position where we started appending */
|
||||
#ifdef HAVE_FTRUNCATE
|
||||
if(ftruncate(fileno(outs->stream), outs->init)) {
|
||||
/* when truncate fails, we can't just append as then we'll
|
||||
create something strange, bail out */
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (23) Failed to truncate file\n");
|
||||
return CURLE_WRITE_ERROR;
|
||||
}
|
||||
/* now seek to the end of the file, the position where we
|
||||
just truncated the file in a large file-safe way */
|
||||
rc = fseek(outs->stream, 0, SEEK_END);
|
||||
#else
|
||||
/* ftruncate is not available, so just reposition the file
|
||||
to the location we would have truncated it. This won't
|
||||
work properly with large files on 32-bit systems, but
|
||||
most of those will have ftruncate. */
|
||||
rc = fseek(outs->stream, (long)outs->init, SEEK_SET);
|
||||
#endif
|
||||
if(rc) {
|
||||
if(global->showerror)
|
||||
fprintf(global->errors,
|
||||
"curl: (23) Failed seeking to end of file\n");
|
||||
return CURLE_WRITE_ERROR;
|
||||
}
|
||||
outs->bytes = 0; /* clear for next round */
|
||||
}
|
||||
*retryp = TRUE;
|
||||
*delay = sleeptime;
|
||||
return CURLE_OK;
|
||||
}
|
||||
} /* if retry_numretries */
|
||||
noretry:
|
||||
|
||||
if((global->progressmode == CURL_PROGRESS_BAR) &&
|
||||
per->progressbar.calls)
|
||||
/* if the custom progress bar has been displayed, we output a
|
||||
newline here */
|
||||
fputs("\n", per->progressbar.out);
|
||||
|
||||
/* Close the outs file */
|
||||
if(outs->fopened && outs->stream) {
|
||||
int rc = fclose(outs->stream);
|
||||
if(!result && rc) {
|
||||
/* something went wrong in the writing process */
|
||||
result = CURLE_WRITE_ERROR;
|
||||
if(global->showerror)
|
||||
fprintf(global->errors, "curl: (%d) Failed writing body\n", result);
|
||||
}
|
||||
if(result && config->rm_partial) {
|
||||
notef(global, "Removing output file: %s", per->outfile);
|
||||
unlink(per->outfile);
|
||||
}
|
||||
}
|
||||
|
||||
/* File time can only be set _after_ the file has been closed */
|
||||
if(!result && config->remote_time && outs->s_isreg && outs->filename) {
|
||||
/* Ask libcurl if we got a remote file time */
|
||||
curl_off_t filetime = -1;
|
||||
curl_easy_getinfo(curl, CURLINFO_FILETIME_T, &filetime);
|
||||
setfiletime(filetime, outs->filename, global);
|
||||
}
|
||||
|
||||
/* Write the --write-out data before cleanup but after result is final */
|
||||
if(config->writeout)
|
||||
ourWriteOut(config->writeout, per, result);
|
||||
|
||||
/* Close function-local opened file descriptors */
|
||||
if(per->heads.fopened && per->heads.stream)
|
||||
fclose(per->heads.stream);
|
||||
|
||||
if(per->heads.alloc_filename)
|
||||
Curl_safefree(per->heads.filename);
|
||||
|
||||
if(per->etag_save.fopened && per->etag_save.stream)
|
||||
fclose(per->etag_save.stream);
|
||||
|
||||
if(per->etag_save.alloc_filename)
|
||||
Curl_safefree(per->etag_save.filename);
|
||||
|
||||
curl_easy_cleanup(per->curl);
|
||||
if(outs->alloc_filename)
|
||||
free(outs->filename);
|
||||
free(per->this_url);
|
||||
free(per->outfile);
|
||||
free(per->uploadfile);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Provenance: curl/curl hostname_check (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 914aaab9153764ef8fa4178215b8ad89d3ac263a
|
||||
* parent: 5c7da89d404bf59c8dd82a001119a16d18365917
|
||||
* commit_url: https://github.com/curl/curl/commit/914aaab9153764ef8fa4178215b8ad89d3ac263a
|
||||
* cve: CVE-2022-27780
|
||||
* license: curl
|
||||
* function: hostname_check
|
||||
* relpath: lib/urlapi.c
|
||||
*/
|
||||
|
||||
static CURLUcode hostname_check(struct Curl_URL *u, char *hostname)
|
||||
{
|
||||
size_t len;
|
||||
size_t hlen = strlen(hostname);
|
||||
|
||||
if(hostname[0] == '[') {
|
||||
const char *l = "0123456789abcdefABCDEF:.";
|
||||
if(hlen < 4) /* '[::]' is the shortest possible valid string */
|
||||
return CURLUE_BAD_IPV6;
|
||||
hostname++;
|
||||
hlen -= 2;
|
||||
|
||||
if(hostname[hlen] != ']')
|
||||
return CURLUE_BAD_IPV6;
|
||||
|
||||
/* only valid letters are ok */
|
||||
len = strspn(hostname, l);
|
||||
if(hlen != len) {
|
||||
hlen = len;
|
||||
if(hostname[len] == '%') {
|
||||
/* this could now be '%[zone id]' */
|
||||
char zoneid[16];
|
||||
int i = 0;
|
||||
char *h = &hostname[len + 1];
|
||||
/* pass '25' if present and is a url encoded percent sign */
|
||||
if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
|
||||
h += 2;
|
||||
while(*h && (*h != ']') && (i < 15))
|
||||
zoneid[i++] = *h++;
|
||||
if(!i || (']' != *h))
|
||||
/* impossible to reach? */
|
||||
return CURLUE_MALFORMED_INPUT;
|
||||
zoneid[i] = 0;
|
||||
u->zoneid = strdup(zoneid);
|
||||
if(!u->zoneid)
|
||||
return CURLUE_OUT_OF_MEMORY;
|
||||
hostname[len] = ']'; /* insert end bracket */
|
||||
hostname[len + 1] = 0; /* terminate the hostname */
|
||||
}
|
||||
else
|
||||
return CURLUE_BAD_IPV6;
|
||||
/* hostname is fine */
|
||||
}
|
||||
#ifdef ENABLE_IPV6
|
||||
{
|
||||
char dest[16]; /* fits a binary IPv6 address */
|
||||
char norm[MAX_IPADR_LEN];
|
||||
hostname[hlen] = 0; /* end the address there */
|
||||
if(1 != Curl_inet_pton(AF_INET6, hostname, dest))
|
||||
return CURLUE_BAD_IPV6;
|
||||
|
||||
/* check if it can be done shorter */
|
||||
if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) &&
|
||||
(strlen(norm) < hlen)) {
|
||||
strcpy(hostname, norm);
|
||||
hlen = strlen(norm);
|
||||
hostname[hlen + 1] = 0;
|
||||
}
|
||||
hostname[hlen] = ']'; /* restore ending bracket */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* letters from the second string are not ok */
|
||||
len = strcspn(hostname, " \r\n\t/:#?!@");
|
||||
if(hlen != len)
|
||||
/* hostname with bad content */
|
||||
return CURLUE_BAD_HOSTNAME;
|
||||
}
|
||||
if(!hostname[0])
|
||||
return CURLUE_NO_HOST;
|
||||
return CURLUE_OK;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Provenance: curl/curl hostname_check (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 5c7da89d404bf59c8dd82a001119a16d18365917
|
||||
* parent: 5c7da89d404bf59c8dd82a001119a16d18365917
|
||||
* commit_url: https://github.com/curl/curl/commit/914aaab9153764ef8fa4178215b8ad89d3ac263a
|
||||
* cve: CVE-2022-27780
|
||||
* license: curl
|
||||
* function: hostname_check
|
||||
* relpath: lib/urlapi.c
|
||||
*/
|
||||
|
||||
static CURLUcode hostname_check(struct Curl_URL *u, char *hostname)
|
||||
{
|
||||
size_t len;
|
||||
size_t hlen = strlen(hostname);
|
||||
|
||||
if(hostname[0] == '[') {
|
||||
const char *l = "0123456789abcdefABCDEF:.";
|
||||
if(hlen < 4) /* '[::]' is the shortest possible valid string */
|
||||
return CURLUE_BAD_IPV6;
|
||||
hostname++;
|
||||
hlen -= 2;
|
||||
|
||||
if(hostname[hlen] != ']')
|
||||
return CURLUE_BAD_IPV6;
|
||||
|
||||
/* only valid letters are ok */
|
||||
len = strspn(hostname, l);
|
||||
if(hlen != len) {
|
||||
hlen = len;
|
||||
if(hostname[len] == '%') {
|
||||
/* this could now be '%[zone id]' */
|
||||
char zoneid[16];
|
||||
int i = 0;
|
||||
char *h = &hostname[len + 1];
|
||||
/* pass '25' if present and is a url encoded percent sign */
|
||||
if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
|
||||
h += 2;
|
||||
while(*h && (*h != ']') && (i < 15))
|
||||
zoneid[i++] = *h++;
|
||||
if(!i || (']' != *h))
|
||||
/* impossible to reach? */
|
||||
return CURLUE_MALFORMED_INPUT;
|
||||
zoneid[i] = 0;
|
||||
u->zoneid = strdup(zoneid);
|
||||
if(!u->zoneid)
|
||||
return CURLUE_OUT_OF_MEMORY;
|
||||
hostname[len] = ']'; /* insert end bracket */
|
||||
hostname[len + 1] = 0; /* terminate the hostname */
|
||||
}
|
||||
else
|
||||
return CURLUE_BAD_IPV6;
|
||||
/* hostname is fine */
|
||||
}
|
||||
#ifdef ENABLE_IPV6
|
||||
{
|
||||
char dest[16]; /* fits a binary IPv6 address */
|
||||
char norm[MAX_IPADR_LEN];
|
||||
hostname[hlen] = 0; /* end the address there */
|
||||
if(1 != Curl_inet_pton(AF_INET6, hostname, dest))
|
||||
return CURLUE_BAD_IPV6;
|
||||
|
||||
/* check if it can be done shorter */
|
||||
if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) &&
|
||||
(strlen(norm) < hlen)) {
|
||||
strcpy(hostname, norm);
|
||||
hlen = strlen(norm);
|
||||
hostname[hlen + 1] = 0;
|
||||
}
|
||||
hostname[hlen] = ']'; /* restore ending bracket */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
/* letters from the second string is not ok */
|
||||
len = strcspn(hostname, " \r\n");
|
||||
if(hlen != len)
|
||||
/* hostname with bad content */
|
||||
return CURLUE_BAD_HOSTNAME;
|
||||
}
|
||||
if(!hostname[0])
|
||||
return CURLUE_NO_HOST;
|
||||
return CURLUE_OK;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Provenance: curl/curl Curl_free_primary_ssl_config (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: Curl_free_primary_ssl_config
|
||||
* relpath: lib/vtls/vtls.c
|
||||
*/
|
||||
|
||||
void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
|
||||
{
|
||||
Curl_safefree(sslc->CApath);
|
||||
Curl_safefree(sslc->CAfile);
|
||||
Curl_safefree(sslc->issuercert);
|
||||
Curl_safefree(sslc->clientcert);
|
||||
Curl_safefree(sslc->random_file);
|
||||
Curl_safefree(sslc->egdsocket);
|
||||
Curl_safefree(sslc->cipher_list);
|
||||
Curl_safefree(sslc->cipher_list13);
|
||||
Curl_safefree(sslc->pinned_key);
|
||||
Curl_safefree(sslc->cert_blob);
|
||||
Curl_safefree(sslc->ca_info_blob);
|
||||
Curl_safefree(sslc->issuercert_blob);
|
||||
Curl_safefree(sslc->curves);
|
||||
Curl_safefree(sslc->CRLfile);
|
||||
#ifdef USE_TLS_SRP
|
||||
Curl_safefree(sslc->username);
|
||||
Curl_safefree(sslc->password);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Provenance: curl/curl Curl_free_primary_ssl_config (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: Curl_free_primary_ssl_config
|
||||
* relpath: lib/vtls/vtls.c
|
||||
*/
|
||||
|
||||
void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
|
||||
{
|
||||
Curl_safefree(sslc->CApath);
|
||||
Curl_safefree(sslc->CAfile);
|
||||
Curl_safefree(sslc->issuercert);
|
||||
Curl_safefree(sslc->clientcert);
|
||||
Curl_safefree(sslc->random_file);
|
||||
Curl_safefree(sslc->egdsocket);
|
||||
Curl_safefree(sslc->cipher_list);
|
||||
Curl_safefree(sslc->cipher_list13);
|
||||
Curl_safefree(sslc->pinned_key);
|
||||
Curl_safefree(sslc->cert_blob);
|
||||
Curl_safefree(sslc->ca_info_blob);
|
||||
Curl_safefree(sslc->issuercert_blob);
|
||||
Curl_safefree(sslc->curves);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Provenance: curl/curl Curl_init_userdefined (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: Curl_init_userdefined
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_init_userdefined(struct Curl_easy *data)
|
||||
{
|
||||
struct UserDefined *set = &data->set;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
set->out = stdout; /* default output to stdout */
|
||||
set->in_set = stdin; /* default input from stdin */
|
||||
set->err = stderr; /* default stderr to stderr */
|
||||
|
||||
/* use fwrite as default function to store output */
|
||||
set->fwrite_func = (curl_write_callback)fwrite;
|
||||
|
||||
/* use fread as default function to read input */
|
||||
set->fread_func_set = (curl_read_callback)fread;
|
||||
set->is_fread_set = 0;
|
||||
set->is_fwrite_set = 0;
|
||||
|
||||
set->seek_func = ZERO_NULL;
|
||||
set->seek_client = ZERO_NULL;
|
||||
|
||||
set->filesize = -1; /* we don't know the size */
|
||||
set->postfieldsize = -1; /* unknown size */
|
||||
set->maxredirs = -1; /* allow any amount by default */
|
||||
|
||||
set->method = HTTPREQ_GET; /* Default HTTP request */
|
||||
set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */
|
||||
set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */
|
||||
set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */
|
||||
set->ftp_filemethod = FTPFILE_MULTICWD;
|
||||
set->ftp_skip_ip = TRUE; /* skip PASV IP by default */
|
||||
#endif
|
||||
set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */
|
||||
|
||||
/* Set the default size of the SSL session ID cache */
|
||||
set->general_ssl.max_ssl_sessions = 5;
|
||||
|
||||
set->proxyport = 0;
|
||||
set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
|
||||
set->httpauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
|
||||
/* SOCKS5 proxy auth defaults to username/password + GSS-API */
|
||||
set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
|
||||
|
||||
/* make libcurl quiet by default: */
|
||||
set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */
|
||||
|
||||
Curl_mime_initpart(&set->mimepost, data);
|
||||
|
||||
/*
|
||||
* libcurl 7.10 introduced SSL verification *by default*! This needs to be
|
||||
* switched off unless wanted.
|
||||
*/
|
||||
#ifndef CURL_DISABLE_DOH
|
||||
set->doh_verifyhost = TRUE;
|
||||
set->doh_verifypeer = TRUE;
|
||||
#endif
|
||||
set->ssl.primary.verifypeer = TRUE;
|
||||
set->ssl.primary.verifyhost = TRUE;
|
||||
#ifdef USE_TLS_SRP
|
||||
set->ssl.primary.authtype = CURL_TLSAUTH_NONE;
|
||||
#endif
|
||||
set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth
|
||||
type */
|
||||
set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
|
||||
default */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
set->proxy_ssl = set->ssl;
|
||||
#endif
|
||||
|
||||
set->new_file_perms = 0644; /* Default permissions */
|
||||
set->new_directory_perms = 0755; /* Default permissions */
|
||||
|
||||
/* for the *protocols fields we don't use the CURLPROTO_ALL convenience
|
||||
define since we internally only use the lower 16 bits for the passed
|
||||
in bitmask to not conflict with the private bits */
|
||||
set->allowed_protocols = CURLPROTO_ALL;
|
||||
set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
|
||||
CURLPROTO_FTPS;
|
||||
|
||||
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
|
||||
/*
|
||||
* disallow unprotected protection negotiation NEC reference implementation
|
||||
* seem not to follow rfc1961 section 4.3/4.4
|
||||
*/
|
||||
set->socks5_gssapi_nec = FALSE;
|
||||
#endif
|
||||
|
||||
/* Set the default CA cert bundle/path detected/specified at build time.
|
||||
*
|
||||
* If Schannel is the selected SSL backend then these locations are
|
||||
* ignored. We allow setting CA location for schannel only when explicitly
|
||||
* specified by the user via CURLOPT_CAINFO / --cacert.
|
||||
*/
|
||||
if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) {
|
||||
#if defined(CURL_CA_BUNDLE)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY],
|
||||
CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
#if defined(CURL_CA_PATH)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
set->wildcard_enabled = FALSE;
|
||||
set->chunk_bgn = ZERO_NULL;
|
||||
set->chunk_end = ZERO_NULL;
|
||||
set->tcp_keepalive = FALSE;
|
||||
set->tcp_keepintvl = 60;
|
||||
set->tcp_keepidle = 60;
|
||||
set->tcp_fastopen = FALSE;
|
||||
set->tcp_nodelay = TRUE;
|
||||
set->ssl_enable_npn = TRUE;
|
||||
set->ssl_enable_alpn = TRUE;
|
||||
set->expect_100_timeout = 1000L; /* Wait for a second by default. */
|
||||
set->sep_headers = TRUE; /* separated header lists by default */
|
||||
set->buffer_size = READBUFFER_SIZE;
|
||||
set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
|
||||
set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
|
||||
set->fnmatch = ZERO_NULL;
|
||||
set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
|
||||
set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
|
||||
set->maxage_conn = 118;
|
||||
set->maxlifetime_conn = 0;
|
||||
set->http09_allowed = FALSE;
|
||||
set->httpwant =
|
||||
#ifdef USE_HTTP2
|
||||
CURL_HTTP_VERSION_2TLS
|
||||
#else
|
||||
CURL_HTTP_VERSION_1_1
|
||||
#endif
|
||||
;
|
||||
Curl_http2_init_userset(set);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Provenance: curl/curl Curl_init_userdefined (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: Curl_init_userdefined
|
||||
* relpath: lib/url.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_init_userdefined(struct Curl_easy *data)
|
||||
{
|
||||
struct UserDefined *set = &data->set;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
set->out = stdout; /* default output to stdout */
|
||||
set->in_set = stdin; /* default input from stdin */
|
||||
set->err = stderr; /* default stderr to stderr */
|
||||
|
||||
/* use fwrite as default function to store output */
|
||||
set->fwrite_func = (curl_write_callback)fwrite;
|
||||
|
||||
/* use fread as default function to read input */
|
||||
set->fread_func_set = (curl_read_callback)fread;
|
||||
set->is_fread_set = 0;
|
||||
set->is_fwrite_set = 0;
|
||||
|
||||
set->seek_func = ZERO_NULL;
|
||||
set->seek_client = ZERO_NULL;
|
||||
|
||||
set->filesize = -1; /* we don't know the size */
|
||||
set->postfieldsize = -1; /* unknown size */
|
||||
set->maxredirs = -1; /* allow any amount by default */
|
||||
|
||||
set->method = HTTPREQ_GET; /* Default HTTP request */
|
||||
set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */
|
||||
set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */
|
||||
set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */
|
||||
set->ftp_filemethod = FTPFILE_MULTICWD;
|
||||
set->ftp_skip_ip = TRUE; /* skip PASV IP by default */
|
||||
#endif
|
||||
set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */
|
||||
|
||||
/* Set the default size of the SSL session ID cache */
|
||||
set->general_ssl.max_ssl_sessions = 5;
|
||||
|
||||
set->proxyport = 0;
|
||||
set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
|
||||
set->httpauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
|
||||
|
||||
/* SOCKS5 proxy auth defaults to username/password + GSS-API */
|
||||
set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
|
||||
|
||||
/* make libcurl quiet by default: */
|
||||
set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */
|
||||
|
||||
Curl_mime_initpart(&set->mimepost, data);
|
||||
|
||||
/*
|
||||
* libcurl 7.10 introduced SSL verification *by default*! This needs to be
|
||||
* switched off unless wanted.
|
||||
*/
|
||||
#ifndef CURL_DISABLE_DOH
|
||||
set->doh_verifyhost = TRUE;
|
||||
set->doh_verifypeer = TRUE;
|
||||
#endif
|
||||
set->ssl.primary.verifypeer = TRUE;
|
||||
set->ssl.primary.verifyhost = TRUE;
|
||||
#ifdef USE_TLS_SRP
|
||||
set->ssl.authtype = CURL_TLSAUTH_NONE;
|
||||
#endif
|
||||
set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth
|
||||
type */
|
||||
set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
|
||||
default */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
set->proxy_ssl = set->ssl;
|
||||
#endif
|
||||
|
||||
set->new_file_perms = 0644; /* Default permissions */
|
||||
set->new_directory_perms = 0755; /* Default permissions */
|
||||
|
||||
/* for the *protocols fields we don't use the CURLPROTO_ALL convenience
|
||||
define since we internally only use the lower 16 bits for the passed
|
||||
in bitmask to not conflict with the private bits */
|
||||
set->allowed_protocols = CURLPROTO_ALL;
|
||||
set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
|
||||
CURLPROTO_FTPS;
|
||||
|
||||
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
|
||||
/*
|
||||
* disallow unprotected protection negotiation NEC reference implementation
|
||||
* seem not to follow rfc1961 section 4.3/4.4
|
||||
*/
|
||||
set->socks5_gssapi_nec = FALSE;
|
||||
#endif
|
||||
|
||||
/* Set the default CA cert bundle/path detected/specified at build time.
|
||||
*
|
||||
* If Schannel is the selected SSL backend then these locations are
|
||||
* ignored. We allow setting CA location for schannel only when explicitly
|
||||
* specified by the user via CURLOPT_CAINFO / --cacert.
|
||||
*/
|
||||
if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) {
|
||||
#if defined(CURL_CA_BUNDLE)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY],
|
||||
CURL_CA_BUNDLE);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
#if defined(CURL_CA_PATH)
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH);
|
||||
if(result)
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
set->wildcard_enabled = FALSE;
|
||||
set->chunk_bgn = ZERO_NULL;
|
||||
set->chunk_end = ZERO_NULL;
|
||||
set->tcp_keepalive = FALSE;
|
||||
set->tcp_keepintvl = 60;
|
||||
set->tcp_keepidle = 60;
|
||||
set->tcp_fastopen = FALSE;
|
||||
set->tcp_nodelay = TRUE;
|
||||
set->ssl_enable_npn = TRUE;
|
||||
set->ssl_enable_alpn = TRUE;
|
||||
set->expect_100_timeout = 1000L; /* Wait for a second by default. */
|
||||
set->sep_headers = TRUE; /* separated header lists by default */
|
||||
set->buffer_size = READBUFFER_SIZE;
|
||||
set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
|
||||
set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
|
||||
set->fnmatch = ZERO_NULL;
|
||||
set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
|
||||
set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
|
||||
set->maxage_conn = 118;
|
||||
set->maxlifetime_conn = 0;
|
||||
set->http09_allowed = FALSE;
|
||||
set->httpwant =
|
||||
#ifdef USE_HTTP2
|
||||
CURL_HTTP_VERSION_2TLS
|
||||
#else
|
||||
CURL_HTTP_VERSION_1_1
|
||||
#endif
|
||||
;
|
||||
Curl_http2_init_userset(set);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Provenance: curl/curl gtls_shutdown (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: gtls_shutdown
|
||||
* relpath: lib/vtls/gtls.c
|
||||
*/
|
||||
|
||||
static int gtls_shutdown(struct Curl_easy *data, struct connectdata *conn,
|
||||
int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
int retval = 0;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
/* This has only been tested on the proftpd server, and the mod_tls code
|
||||
sends a close notify alert without waiting for a close notify alert in
|
||||
response. Thus we wait for a close notify alert from the server, but
|
||||
we do not send one. Let's hope other servers do the same... */
|
||||
|
||||
if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
|
||||
gnutls_bye(backend->session, GNUTLS_SHUT_WR);
|
||||
#endif
|
||||
|
||||
if(backend->session) {
|
||||
ssize_t result;
|
||||
bool done = FALSE;
|
||||
char buf[120];
|
||||
|
||||
while(!done) {
|
||||
int what = SOCKET_READABLE(conn->sock[sockindex],
|
||||
SSL_SHUTDOWN_TIMEOUT);
|
||||
if(what > 0) {
|
||||
/* Something to read, let's do it and hope that it is the close
|
||||
notify alert from the server */
|
||||
result = gnutls_record_recv(backend->session,
|
||||
buf, sizeof(buf));
|
||||
switch(result) {
|
||||
case 0:
|
||||
/* This is the expected response. There was no data but only
|
||||
the close notify alert */
|
||||
done = TRUE;
|
||||
break;
|
||||
case GNUTLS_E_AGAIN:
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED");
|
||||
break;
|
||||
default:
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(0 == what) {
|
||||
/* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
done = TRUE;
|
||||
}
|
||||
else {
|
||||
/* anything that gets here is fatally bad */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
}
|
||||
}
|
||||
gnutls_deinit(backend->session);
|
||||
}
|
||||
gnutls_certificate_free_credentials(backend->cred);
|
||||
|
||||
#ifdef USE_GNUTLS_SRP
|
||||
if(SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP
|
||||
&& SSL_SET_OPTION(primary.username) != NULL)
|
||||
gnutls_srp_free_client_credentials(backend->srp_client_cred);
|
||||
#endif
|
||||
|
||||
backend->cred = NULL;
|
||||
backend->session = NULL;
|
||||
|
||||
return retval;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Provenance: curl/curl gtls_shutdown (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: gtls_shutdown
|
||||
* relpath: lib/vtls/gtls.c
|
||||
*/
|
||||
|
||||
static int gtls_shutdown(struct Curl_easy *data, struct connectdata *conn,
|
||||
int sockindex)
|
||||
{
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
int retval = 0;
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
/* This has only been tested on the proftpd server, and the mod_tls code
|
||||
sends a close notify alert without waiting for a close notify alert in
|
||||
response. Thus we wait for a close notify alert from the server, but
|
||||
we do not send one. Let's hope other servers do the same... */
|
||||
|
||||
if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
|
||||
gnutls_bye(backend->session, GNUTLS_SHUT_WR);
|
||||
#endif
|
||||
|
||||
if(backend->session) {
|
||||
ssize_t result;
|
||||
bool done = FALSE;
|
||||
char buf[120];
|
||||
|
||||
while(!done) {
|
||||
int what = SOCKET_READABLE(conn->sock[sockindex],
|
||||
SSL_SHUTDOWN_TIMEOUT);
|
||||
if(what > 0) {
|
||||
/* Something to read, let's do it and hope that it is the close
|
||||
notify alert from the server */
|
||||
result = gnutls_record_recv(backend->session,
|
||||
buf, sizeof(buf));
|
||||
switch(result) {
|
||||
case 0:
|
||||
/* This is the expected response. There was no data but only
|
||||
the close notify alert */
|
||||
done = TRUE;
|
||||
break;
|
||||
case GNUTLS_E_AGAIN:
|
||||
case GNUTLS_E_INTERRUPTED:
|
||||
infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED");
|
||||
break;
|
||||
default:
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(0 == what) {
|
||||
/* timeout */
|
||||
failf(data, "SSL shutdown timeout");
|
||||
done = TRUE;
|
||||
}
|
||||
else {
|
||||
/* anything that gets here is fatally bad */
|
||||
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
|
||||
retval = -1;
|
||||
done = TRUE;
|
||||
}
|
||||
}
|
||||
gnutls_deinit(backend->session);
|
||||
}
|
||||
gnutls_certificate_free_credentials(backend->cred);
|
||||
|
||||
#ifdef USE_GNUTLS_SRP
|
||||
if(SSL_SET_OPTION(authtype) == CURL_TLSAUTH_SRP
|
||||
&& SSL_SET_OPTION(username) != NULL)
|
||||
gnutls_srp_free_client_credentials(backend->srp_client_cred);
|
||||
#endif
|
||||
|
||||
backend->cred = NULL;
|
||||
backend->session = NULL;
|
||||
|
||||
return retval;
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/* Provenance: curl/curl nss_setup_connect (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: nss_setup_connect
|
||||
* relpath: lib/vtls/nss.c
|
||||
*/
|
||||
|
||||
static CURLcode nss_setup_connect(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
PRFileDesc *model = NULL;
|
||||
PRFileDesc *nspr_io = NULL;
|
||||
PRFileDesc *nspr_io_stub = NULL;
|
||||
PRBool ssl_no_cache;
|
||||
PRBool ssl_cbc_random_iv;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
CURLcode result;
|
||||
bool second_layer = FALSE;
|
||||
SSLVersionRange sslver_supported;
|
||||
SSLVersionRange sslver = {
|
||||
SSL_LIBRARY_VERSION_TLS_1_0, /* min */
|
||||
#ifdef SSL_LIBRARY_VERSION_TLS_1_3
|
||||
SSL_LIBRARY_VERSION_TLS_1_3 /* max */
|
||||
#elif defined SSL_LIBRARY_VERSION_TLS_1_2
|
||||
SSL_LIBRARY_VERSION_TLS_1_2
|
||||
#elif defined SSL_LIBRARY_VERSION_TLS_1_1
|
||||
SSL_LIBRARY_VERSION_TLS_1_1
|
||||
#else
|
||||
SSL_LIBRARY_VERSION_TLS_1_0
|
||||
#endif
|
||||
};
|
||||
char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
|
||||
if(!snihost) {
|
||||
failf(data, "Failed to set SNI");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
backend->data = data;
|
||||
|
||||
/* list of all NSS objects we need to destroy in nss_do_close() */
|
||||
Curl_llist_init(&backend->obj_list, nss_destroy_object);
|
||||
|
||||
PR_Lock(nss_initlock);
|
||||
result = nss_setup(data);
|
||||
if(result) {
|
||||
PR_Unlock(nss_initlock);
|
||||
goto error;
|
||||
}
|
||||
|
||||
PK11_SetPasswordFunc(nss_get_password);
|
||||
|
||||
result = nss_load_module(&pem_module, pem_library, "PEM");
|
||||
PR_Unlock(nss_initlock);
|
||||
if(result == CURLE_FAILED_INIT)
|
||||
infof(data, "WARNING: failed to load NSS PEM library %s. Using "
|
||||
"OpenSSL PEM certificates will not work.", pem_library);
|
||||
else if(result)
|
||||
goto error;
|
||||
|
||||
result = CURLE_SSL_CONNECT_ERROR;
|
||||
|
||||
model = PR_NewTCPSocket();
|
||||
if(!model)
|
||||
goto error;
|
||||
model = SSL_ImportFD(NULL, model);
|
||||
|
||||
if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
|
||||
goto error;
|
||||
if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* do not use SSL cache if disabled or we are not going to verify peer */
|
||||
ssl_no_cache = (SSL_SET_OPTION(primary.sessionid)
|
||||
&& SSL_CONN_CONFIG(verifypeer)) ? PR_FALSE : PR_TRUE;
|
||||
if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* enable/disable the requested SSL version(s) */
|
||||
if(nss_init_sslver(&sslver, data, conn) != CURLE_OK)
|
||||
goto error;
|
||||
if(SSL_VersionRangeGetSupported(ssl_variant_stream,
|
||||
&sslver_supported) != SECSuccess)
|
||||
goto error;
|
||||
if(sslver_supported.max < sslver.max && sslver_supported.max >= sslver.min) {
|
||||
char *sslver_req_str, *sslver_supp_str;
|
||||
sslver_req_str = nss_sslver_to_name(sslver.max);
|
||||
sslver_supp_str = nss_sslver_to_name(sslver_supported.max);
|
||||
if(sslver_req_str && sslver_supp_str)
|
||||
infof(data, "Falling back from %s to max supported SSL version (%s)",
|
||||
sslver_req_str, sslver_supp_str);
|
||||
free(sslver_req_str);
|
||||
free(sslver_supp_str);
|
||||
sslver.max = sslver_supported.max;
|
||||
}
|
||||
if(SSL_VersionRangeSet(model, &sslver) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
ssl_cbc_random_iv = !SSL_SET_OPTION(enable_beast);
|
||||
#ifdef SSL_CBC_RANDOM_IV
|
||||
/* unless the user explicitly asks to allow the protocol vulnerability, we
|
||||
use the work-around */
|
||||
if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess)
|
||||
infof(data, "WARNING: failed to set SSL_CBC_RANDOM_IV = %d",
|
||||
ssl_cbc_random_iv);
|
||||
#else
|
||||
if(ssl_cbc_random_iv)
|
||||
infof(data, "WARNING: support for SSL_CBC_RANDOM_IV not compiled in");
|
||||
#endif
|
||||
|
||||
if(SSL_CONN_CONFIG(cipher_list)) {
|
||||
if(set_ciphers(data, model, SSL_CONN_CONFIG(cipher_list)) != SECSuccess) {
|
||||
result = CURLE_SSL_CIPHER;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if(!SSL_CONN_CONFIG(verifypeer) && SSL_CONN_CONFIG(verifyhost))
|
||||
infof(data, "WARNING: ignoring value of ssl.verifyhost");
|
||||
|
||||
/* bypass the default SSL_AuthCertificate() hook in case we do not want to
|
||||
* verify peer */
|
||||
if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* not checked yet */
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = 0;
|
||||
|
||||
if(SSL_BadCertHook(model, BadCertHandler, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
if(SSL_HandshakeCallback(model, HandshakeCallback, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
{
|
||||
const CURLcode rv = nss_load_ca_certificates(data, conn, sockindex);
|
||||
if((rv == CURLE_SSL_CACERT_BADFILE) && !SSL_CONN_CONFIG(verifypeer))
|
||||
/* not a fatal error because we are not going to verify the peer */
|
||||
infof(data, "WARNING: CA certificates failed to load");
|
||||
else if(rv) {
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(primary.CRLfile)) {
|
||||
const CURLcode rv = nss_load_crl(SSL_SET_OPTION(primary.CRLfile));
|
||||
if(rv) {
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
infof(data, " CRLfile: %s", SSL_SET_OPTION(primary.CRLfile));
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(primary.clientcert)) {
|
||||
char *nickname = dup_nickname(data, SSL_SET_OPTION(primary.clientcert));
|
||||
if(nickname) {
|
||||
/* we are not going to use libnsspem.so to read the client cert */
|
||||
backend->obj_clicert = NULL;
|
||||
}
|
||||
else {
|
||||
CURLcode rv = cert_stuff(data, conn, sockindex,
|
||||
SSL_SET_OPTION(primary.clientcert),
|
||||
SSL_SET_OPTION(key));
|
||||
if(rv) {
|
||||
/* failf() is already done in cert_stuff() */
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* store the nickname for SelectClientCert() called during handshake */
|
||||
backend->client_nickname = nickname;
|
||||
}
|
||||
else
|
||||
backend->client_nickname = NULL;
|
||||
|
||||
if(SSL_GetClientAuthDataHook(model, SelectClientCert,
|
||||
(void *)connssl) != SECSuccess) {
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
struct ssl_backend_data *proxy_backend;
|
||||
proxy_backend = conn->proxy_ssl[sockindex].backend;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(proxy_backend);
|
||||
DEBUGASSERT(proxy_backend->handle);
|
||||
nspr_io = proxy_backend->handle;
|
||||
second_layer = TRUE;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
/* wrap OS file descriptor by NSPR's file descriptor abstraction */
|
||||
nspr_io = PR_ImportTCPSocket(sockfd);
|
||||
if(!nspr_io)
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* create our own NSPR I/O layer */
|
||||
nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods);
|
||||
if(!nspr_io_stub) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* make the per-connection data accessible from NSPR I/O callbacks */
|
||||
nspr_io_stub->secret = (void *)connssl;
|
||||
|
||||
/* push our new layer to the NSPR I/O stack */
|
||||
if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
PR_Close(nspr_io_stub);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* import our model socket onto the current I/O stack */
|
||||
backend->handle = SSL_ImportFD(model, nspr_io);
|
||||
if(!backend->handle) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
goto error;
|
||||
}
|
||||
|
||||
PR_Close(model); /* We don't need this any more */
|
||||
model = NULL;
|
||||
|
||||
/* This is the password associated with the cert that we're using */
|
||||
if(SSL_SET_OPTION(key_passwd)) {
|
||||
SSL_SetPKCS11PinArg(backend->handle, SSL_SET_OPTION(key_passwd));
|
||||
}
|
||||
|
||||
#ifdef SSL_ENABLE_OCSP_STAPLING
|
||||
if(SSL_CONN_CONFIG(verifystatus)) {
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SSL_ENABLE_NPN
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_NPN, conn->bits.tls_enable_npn
|
||||
? PR_TRUE : PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_ENABLE_ALPN
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_ALPN, conn->bits.tls_enable_alpn
|
||||
? PR_TRUE : PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
|
||||
if(data->set.ssl.falsestart) {
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_FALSE_START, PR_TRUE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
|
||||
if(SSL_SetCanFalseStartCallback(backend->handle, CanFalseStartCallback,
|
||||
data) != SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN)
|
||||
if(conn->bits.tls_enable_npn || conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
}
|
||||
#endif
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
|
||||
if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Force handshake on next I/O */
|
||||
if(SSL_ResetHandshake(backend->handle, /* asServer */ PR_FALSE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* propagate hostname to the TLS layer */
|
||||
if(SSL_SetURL(backend->handle, snihost) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* prevent NSS from re-using the session for a different hostname */
|
||||
if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
if(model)
|
||||
PR_Close(model);
|
||||
|
||||
return nss_fail_connect(connssl, data, result);
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/* Provenance: curl/curl nss_setup_connect (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: nss_setup_connect
|
||||
* relpath: lib/vtls/nss.c
|
||||
*/
|
||||
|
||||
static CURLcode nss_setup_connect(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
PRFileDesc *model = NULL;
|
||||
PRFileDesc *nspr_io = NULL;
|
||||
PRFileDesc *nspr_io_stub = NULL;
|
||||
PRBool ssl_no_cache;
|
||||
PRBool ssl_cbc_random_iv;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
CURLcode result;
|
||||
bool second_layer = FALSE;
|
||||
SSLVersionRange sslver_supported;
|
||||
SSLVersionRange sslver = {
|
||||
SSL_LIBRARY_VERSION_TLS_1_0, /* min */
|
||||
#ifdef SSL_LIBRARY_VERSION_TLS_1_3
|
||||
SSL_LIBRARY_VERSION_TLS_1_3 /* max */
|
||||
#elif defined SSL_LIBRARY_VERSION_TLS_1_2
|
||||
SSL_LIBRARY_VERSION_TLS_1_2
|
||||
#elif defined SSL_LIBRARY_VERSION_TLS_1_1
|
||||
SSL_LIBRARY_VERSION_TLS_1_1
|
||||
#else
|
||||
SSL_LIBRARY_VERSION_TLS_1_0
|
||||
#endif
|
||||
};
|
||||
char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
|
||||
if(!snihost) {
|
||||
failf(data, "Failed to set SNI");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
backend->data = data;
|
||||
|
||||
/* list of all NSS objects we need to destroy in nss_do_close() */
|
||||
Curl_llist_init(&backend->obj_list, nss_destroy_object);
|
||||
|
||||
PR_Lock(nss_initlock);
|
||||
result = nss_setup(data);
|
||||
if(result) {
|
||||
PR_Unlock(nss_initlock);
|
||||
goto error;
|
||||
}
|
||||
|
||||
PK11_SetPasswordFunc(nss_get_password);
|
||||
|
||||
result = nss_load_module(&pem_module, pem_library, "PEM");
|
||||
PR_Unlock(nss_initlock);
|
||||
if(result == CURLE_FAILED_INIT)
|
||||
infof(data, "WARNING: failed to load NSS PEM library %s. Using "
|
||||
"OpenSSL PEM certificates will not work.", pem_library);
|
||||
else if(result)
|
||||
goto error;
|
||||
|
||||
result = CURLE_SSL_CONNECT_ERROR;
|
||||
|
||||
model = PR_NewTCPSocket();
|
||||
if(!model)
|
||||
goto error;
|
||||
model = SSL_ImportFD(NULL, model);
|
||||
|
||||
if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
|
||||
goto error;
|
||||
if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* do not use SSL cache if disabled or we are not going to verify peer */
|
||||
ssl_no_cache = (SSL_SET_OPTION(primary.sessionid)
|
||||
&& SSL_CONN_CONFIG(verifypeer)) ? PR_FALSE : PR_TRUE;
|
||||
if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* enable/disable the requested SSL version(s) */
|
||||
if(nss_init_sslver(&sslver, data, conn) != CURLE_OK)
|
||||
goto error;
|
||||
if(SSL_VersionRangeGetSupported(ssl_variant_stream,
|
||||
&sslver_supported) != SECSuccess)
|
||||
goto error;
|
||||
if(sslver_supported.max < sslver.max && sslver_supported.max >= sslver.min) {
|
||||
char *sslver_req_str, *sslver_supp_str;
|
||||
sslver_req_str = nss_sslver_to_name(sslver.max);
|
||||
sslver_supp_str = nss_sslver_to_name(sslver_supported.max);
|
||||
if(sslver_req_str && sslver_supp_str)
|
||||
infof(data, "Falling back from %s to max supported SSL version (%s)",
|
||||
sslver_req_str, sslver_supp_str);
|
||||
free(sslver_req_str);
|
||||
free(sslver_supp_str);
|
||||
sslver.max = sslver_supported.max;
|
||||
}
|
||||
if(SSL_VersionRangeSet(model, &sslver) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
ssl_cbc_random_iv = !SSL_SET_OPTION(enable_beast);
|
||||
#ifdef SSL_CBC_RANDOM_IV
|
||||
/* unless the user explicitly asks to allow the protocol vulnerability, we
|
||||
use the work-around */
|
||||
if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess)
|
||||
infof(data, "WARNING: failed to set SSL_CBC_RANDOM_IV = %d",
|
||||
ssl_cbc_random_iv);
|
||||
#else
|
||||
if(ssl_cbc_random_iv)
|
||||
infof(data, "WARNING: support for SSL_CBC_RANDOM_IV not compiled in");
|
||||
#endif
|
||||
|
||||
if(SSL_CONN_CONFIG(cipher_list)) {
|
||||
if(set_ciphers(data, model, SSL_CONN_CONFIG(cipher_list)) != SECSuccess) {
|
||||
result = CURLE_SSL_CIPHER;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if(!SSL_CONN_CONFIG(verifypeer) && SSL_CONN_CONFIG(verifyhost))
|
||||
infof(data, "WARNING: ignoring value of ssl.verifyhost");
|
||||
|
||||
/* bypass the default SSL_AuthCertificate() hook in case we do not want to
|
||||
* verify peer */
|
||||
if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* not checked yet */
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = 0;
|
||||
|
||||
if(SSL_BadCertHook(model, BadCertHandler, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
if(SSL_HandshakeCallback(model, HandshakeCallback, data) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
{
|
||||
const CURLcode rv = nss_load_ca_certificates(data, conn, sockindex);
|
||||
if((rv == CURLE_SSL_CACERT_BADFILE) && !SSL_CONN_CONFIG(verifypeer))
|
||||
/* not a fatal error because we are not going to verify the peer */
|
||||
infof(data, "WARNING: CA certificates failed to load");
|
||||
else if(rv) {
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(CRLfile)) {
|
||||
const CURLcode rv = nss_load_crl(SSL_SET_OPTION(CRLfile));
|
||||
if(rv) {
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
infof(data, " CRLfile: %s", SSL_SET_OPTION(CRLfile));
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(primary.clientcert)) {
|
||||
char *nickname = dup_nickname(data, SSL_SET_OPTION(primary.clientcert));
|
||||
if(nickname) {
|
||||
/* we are not going to use libnsspem.so to read the client cert */
|
||||
backend->obj_clicert = NULL;
|
||||
}
|
||||
else {
|
||||
CURLcode rv = cert_stuff(data, conn, sockindex,
|
||||
SSL_SET_OPTION(primary.clientcert),
|
||||
SSL_SET_OPTION(key));
|
||||
if(rv) {
|
||||
/* failf() is already done in cert_stuff() */
|
||||
result = rv;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* store the nickname for SelectClientCert() called during handshake */
|
||||
backend->client_nickname = nickname;
|
||||
}
|
||||
else
|
||||
backend->client_nickname = NULL;
|
||||
|
||||
if(SSL_GetClientAuthDataHook(model, SelectClientCert,
|
||||
(void *)connssl) != SECSuccess) {
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
struct ssl_backend_data *proxy_backend;
|
||||
proxy_backend = conn->proxy_ssl[sockindex].backend;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(proxy_backend);
|
||||
DEBUGASSERT(proxy_backend->handle);
|
||||
nspr_io = proxy_backend->handle;
|
||||
second_layer = TRUE;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
/* wrap OS file descriptor by NSPR's file descriptor abstraction */
|
||||
nspr_io = PR_ImportTCPSocket(sockfd);
|
||||
if(!nspr_io)
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* create our own NSPR I/O layer */
|
||||
nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods);
|
||||
if(!nspr_io_stub) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* make the per-connection data accessible from NSPR I/O callbacks */
|
||||
nspr_io_stub->secret = (void *)connssl;
|
||||
|
||||
/* push our new layer to the NSPR I/O stack */
|
||||
if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
PR_Close(nspr_io_stub);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* import our model socket onto the current I/O stack */
|
||||
backend->handle = SSL_ImportFD(model, nspr_io);
|
||||
if(!backend->handle) {
|
||||
if(!second_layer)
|
||||
PR_Close(nspr_io);
|
||||
goto error;
|
||||
}
|
||||
|
||||
PR_Close(model); /* We don't need this any more */
|
||||
model = NULL;
|
||||
|
||||
/* This is the password associated with the cert that we're using */
|
||||
if(SSL_SET_OPTION(key_passwd)) {
|
||||
SSL_SetPKCS11PinArg(backend->handle, SSL_SET_OPTION(key_passwd));
|
||||
}
|
||||
|
||||
#ifdef SSL_ENABLE_OCSP_STAPLING
|
||||
if(SSL_CONN_CONFIG(verifystatus)) {
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SSL_ENABLE_NPN
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_NPN, conn->bits.tls_enable_npn
|
||||
? PR_TRUE : PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_ENABLE_ALPN
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_ALPN, conn->bits.tls_enable_alpn
|
||||
? PR_TRUE : PR_FALSE) != SECSuccess)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
|
||||
if(data->set.ssl.falsestart) {
|
||||
if(SSL_OptionSet(backend->handle, SSL_ENABLE_FALSE_START, PR_TRUE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
|
||||
if(SSL_SetCanFalseStartCallback(backend->handle, CanFalseStartCallback,
|
||||
data) != SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN)
|
||||
if(conn->bits.tls_enable_npn || conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
}
|
||||
#endif
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
|
||||
if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Force handshake on next I/O */
|
||||
if(SSL_ResetHandshake(backend->handle, /* asServer */ PR_FALSE)
|
||||
!= SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* propagate hostname to the TLS layer */
|
||||
if(SSL_SetURL(backend->handle, snihost) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
/* prevent NSS from re-using the session for a different hostname */
|
||||
if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess)
|
||||
goto error;
|
||||
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
if(model)
|
||||
PR_Close(model);
|
||||
|
||||
return nss_fail_connect(connssl, data, result);
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
/* Provenance: curl/curl ossl_connect_step1 (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: ossl_connect_step1
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode ossl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
char *ciphers;
|
||||
SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
|
||||
X509_LOOKUP *lookup = NULL;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
ctx_option_t ctx_options = 0;
|
||||
void *ssl_sessionid = NULL;
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
bool sni;
|
||||
const char * const hostname = SSL_HOST_NAME();
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
#endif
|
||||
const long int ssl_version = SSL_CONN_CONFIG(version);
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(primary.authtype);
|
||||
#endif
|
||||
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
|
||||
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
|
||||
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
|
||||
const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
|
||||
const char * const ssl_cafile =
|
||||
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
|
||||
(ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
|
||||
const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const char * const ssl_crlfile = SSL_SET_OPTION(primary.CRLfile);
|
||||
char error_buffer[256];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
bool imported_native_ca = false;
|
||||
|
||||
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
/* Make funny stuff to get random input */
|
||||
result = ossl_seed(data);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK;
|
||||
|
||||
/* check to see if we've been told to use an explicit SSL/TLS version */
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
case CURL_SSLVERSION_TLSv1_3:
|
||||
/* it will be handled later with the context options */
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
|
||||
req_method = TLS_client_method();
|
||||
#else
|
||||
req_method = SSLv23_client_method();
|
||||
#endif
|
||||
use_sni(TRUE);
|
||||
break;
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "No SSLv2 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "No SSLv3 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
DEBUGASSERT(!backend->ctx);
|
||||
backend->ctx = SSL_CTX_new(req_method);
|
||||
|
||||
if(!backend->ctx) {
|
||||
failf(data, "SSL: couldn't create a context: %s",
|
||||
ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTRL_SET_MSG_CALLBACK
|
||||
if(data->set.fdebug && data->set.verbose) {
|
||||
/* the SSL trace callback is only used for verbose logging */
|
||||
SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
|
||||
SSL_CTX_set_msg_callback_arg(backend->ctx, conn);
|
||||
set_logger(conn, data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* OpenSSL contains code to work around lots of bugs and flaws in various
|
||||
SSL-implementations. SSL_CTX_set_options() is used to enabled those
|
||||
work-arounds. The man page for this option states that SSL_OP_ALL enables
|
||||
all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
|
||||
enable the bug workaround options if compatibility with somewhat broken
|
||||
implementations is desired."
|
||||
|
||||
The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to
|
||||
disable "rfc4507bis session ticket support". rfc4507bis was later turned
|
||||
into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077
|
||||
|
||||
The enabled extension concerns the session management. I wonder how often
|
||||
libcurl stops a connection and then resumes a TLS session. Also, sending
|
||||
the session data is some overhead. I suggest that you just use your
|
||||
proposed patch (which explicitly disables TICKET).
|
||||
|
||||
If someone writes an application with libcurl and OpenSSL who wants to
|
||||
enable the feature, one can do this in the SSL callback.
|
||||
|
||||
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
|
||||
interoperability with web server Netscape Enterprise Server 2.0.1 which
|
||||
was released back in 1996.
|
||||
|
||||
Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
|
||||
become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
|
||||
CVE-2010-4180 when using previous OpenSSL versions we no longer enable
|
||||
this option regardless of OpenSSL version and SSL_OP_ALL definition.
|
||||
|
||||
OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
|
||||
(https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
|
||||
SSL_OP_ALL that _disables_ that work-around despite the fact that
|
||||
SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
|
||||
keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
|
||||
must not be set.
|
||||
*/
|
||||
|
||||
ctx_options = SSL_OP_ALL;
|
||||
|
||||
#ifdef SSL_OP_NO_TICKET
|
||||
ctx_options |= SSL_OP_NO_TICKET;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
ctx_options |= SSL_OP_NO_COMPRESSION;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
||||
/* mitigate CVE-2010-4180 */
|
||||
ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
/* unless the user explicitly asks to allow the protocol vulnerability we
|
||||
use the work-around */
|
||||
if(!SSL_SET_OPTION(enable_beast))
|
||||
ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
||||
#endif
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
|
||||
/* "--tlsv<x.y>" options mean TLS >= version <x.y> */
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
|
||||
case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
|
||||
case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
|
||||
/* asking for any TLS version as the minimum, means no SSL versions
|
||||
allowed */
|
||||
ctx_options |= SSL_OP_NO_SSLv2;
|
||||
ctx_options |= SSL_OP_NO_SSLv3;
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
|
||||
result = set_ssl_version_min_max(backend->ctx, conn);
|
||||
#else
|
||||
result = set_ssl_version_min_max_legacy(&ctx_options, data, conn,
|
||||
sockindex);
|
||||
#endif
|
||||
if(result != CURLE_OK)
|
||||
return result;
|
||||
break;
|
||||
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(backend->ctx, ctx_options);
|
||||
|
||||
#ifdef HAS_NPN
|
||||
if(conn->bits.tls_enable_npn)
|
||||
SSL_CTX_set_next_proto_select_cb(backend->ctx, select_next_proto_cb, data);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ALPN
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
|
||||
}
|
||||
#endif
|
||||
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
|
||||
|
||||
/* expects length prefixed preference ordered list of protocols in wire
|
||||
* format
|
||||
*/
|
||||
if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
|
||||
failf(data, "Error setting ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
|
||||
if(!result &&
|
||||
!cert_stuff(data, backend->ctx,
|
||||
ssl_cert, ssl_cert_blob, ssl_cert_type,
|
||||
SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
|
||||
SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd)))
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
if(result)
|
||||
/* failf() is already done in cert_stuff() */
|
||||
return result;
|
||||
}
|
||||
|
||||
ciphers = SSL_CONN_CONFIG(cipher_list);
|
||||
if(!ciphers)
|
||||
ciphers = (char *)DEFAULT_CIPHER_SELECTION;
|
||||
if(ciphers) {
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
|
||||
failf(data, "failed setting cipher list: %s", ciphers);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "Cipher selection: %s", ciphers);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
|
||||
{
|
||||
char *ciphers13 = SSL_CONN_CONFIG(cipher_list13);
|
||||
if(ciphers13) {
|
||||
if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) {
|
||||
failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "TLS 1.3 cipher selection: %s", ciphers13);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
|
||||
/* OpenSSL 1.1.1 requires clients to opt-in for PHA */
|
||||
SSL_CTX_set_post_handshake_auth(backend->ctx, 1);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_EC_CURVES
|
||||
{
|
||||
char *curves = SSL_CONN_CONFIG(curves);
|
||||
if(curves) {
|
||||
if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
|
||||
failf(data, "failed setting curves list: '%s'", curves);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
if((ssl_authtype == CURL_TLSAUTH_SRP) &&
|
||||
Curl_allow_auth_to_host(data)) {
|
||||
char * const ssl_username = SSL_SET_OPTION(primary.username);
|
||||
char * const ssl_password = SSL_SET_OPTION(primary.password);
|
||||
infof(data, "Using TLS-SRP username: %s", ssl_username);
|
||||
|
||||
if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) {
|
||||
failf(data, "Unable to set SRP user name");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) {
|
||||
failf(data, "failed setting SRP password");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CONN_CONFIG(cipher_list)) {
|
||||
infof(data, "Setting cipher list SRP");
|
||||
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) {
|
||||
failf(data, "failed setting SRP cipher list");
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(USE_WIN32_CRYPTO)
|
||||
/* Import certificates from the Windows root certificate store if requested.
|
||||
https://stackoverflow.com/questions/9507184/
|
||||
https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
|
||||
https://datatracker.ietf.org/doc/html/rfc5280 */
|
||||
if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) &&
|
||||
(SSL_SET_OPTION(native_ca_store))) {
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
|
||||
|
||||
if(hStore) {
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
/* The array of enhanced key usage OIDs will vary per certificate and is
|
||||
declared outside of the loop so that rather than malloc/free each
|
||||
iteration we can grow it with realloc, when necessary. */
|
||||
CERT_ENHKEY_USAGE *enhkey_usage = NULL;
|
||||
DWORD enhkey_usage_size = 0;
|
||||
|
||||
/* This loop makes a best effort to import all valid certificates from
|
||||
the MS root store. If a certificate cannot be imported it is skipped.
|
||||
'result' is used to store only hard-fail conditions (such as out of
|
||||
memory) that cause an early break. */
|
||||
result = CURLE_OK;
|
||||
for(;;) {
|
||||
X509 *x509;
|
||||
FILETIME now;
|
||||
BYTE key_usage[2];
|
||||
DWORD req_size;
|
||||
const unsigned char *encoded_cert;
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
char cert_name[256];
|
||||
#endif
|
||||
|
||||
pContext = CertEnumCertificatesInStore(hStore, pContext);
|
||||
if(!pContext)
|
||||
break;
|
||||
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
|
||||
NULL, cert_name, sizeof(cert_name))) {
|
||||
strcpy(cert_name, "Unknown");
|
||||
}
|
||||
infof(data, "SSL: Checking cert \"%s\"", cert_name);
|
||||
#endif
|
||||
|
||||
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
|
||||
if(!encoded_cert)
|
||||
continue;
|
||||
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
|
||||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
|
||||
continue;
|
||||
|
||||
/* If key usage exists check for signing attribute */
|
||||
if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
|
||||
pContext->pCertInfo,
|
||||
key_usage, sizeof(key_usage))) {
|
||||
if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
|
||||
continue;
|
||||
}
|
||||
else if(GetLastError())
|
||||
continue;
|
||||
|
||||
/* If enhanced key usage exists check for server auth attribute.
|
||||
*
|
||||
* Note "In a Microsoft environment, a certificate might also have EKU
|
||||
* extended properties that specify valid uses for the certificate."
|
||||
* The call below checks both, and behavior varies depending on what is
|
||||
* found. For more details see CertGetEnhancedKeyUsage doc.
|
||||
*/
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
|
||||
if(req_size && req_size > enhkey_usage_size) {
|
||||
void *tmp = realloc(enhkey_usage, req_size);
|
||||
|
||||
if(!tmp) {
|
||||
failf(data, "SSL: Out of memory allocating for OID list");
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
|
||||
enhkey_usage_size = req_size;
|
||||
}
|
||||
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
|
||||
if(!enhkey_usage->cUsageIdentifier) {
|
||||
/* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
|
||||
good for all uses. If it returns zero, the certificate has no
|
||||
valid uses." */
|
||||
if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
DWORD i;
|
||||
bool found = false;
|
||||
|
||||
for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
|
||||
if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
|
||||
enhkey_usage->rgpszUsageIdentifier[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if(!x509)
|
||||
continue;
|
||||
|
||||
/* Try to import the certificate. This may fail for legitimate reasons
|
||||
such as duplicate certificate, which is allowed by MS but not
|
||||
OpenSSL. */
|
||||
if(X509_STORE_add_cert(store, x509) == 1) {
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
infof(data, "SSL: Imported cert \"%s\"", cert_name);
|
||||
#endif
|
||||
imported_native_ca = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported Windows CA store");
|
||||
else
|
||||
infof(data, "error importing Windows CA store, continuing anyway");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(backend->ctx, ca_info_blob);
|
||||
if(result) {
|
||||
if(result == CURLE_OUT_OF_MEMORY ||
|
||||
(verifypeer && !imported_native_ca)) {
|
||||
failf(data, "error importing CA certificate blob");
|
||||
return result;
|
||||
}
|
||||
/* Only warn if no certificate verification is required. */
|
||||
infof(data, "error importing CA certificate blob, continuing anyway");
|
||||
}
|
||||
}
|
||||
|
||||
if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) {
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
if(ssl_cafile &&
|
||||
!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
if(ssl_capath &&
|
||||
!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
#else
|
||||
/* tell OpenSSL where to find CA certificates that are used to verify the
|
||||
server's certificate. */
|
||||
if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
#endif
|
||||
infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
if(verifypeer &&
|
||||
!ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built-in default as fallback */
|
||||
SSL_CTX_set_default_verify_paths(backend->ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_crlfile) {
|
||||
/* tell OpenSSL where to find CRL file that is used to check certificate
|
||||
* revocation */
|
||||
lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_LOOKUP_file());
|
||||
if(!lookup ||
|
||||
(!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
|
||||
failf(data, "error loading CRL file: %s", ssl_crlfile);
|
||||
return CURLE_SSL_CRL_BADFILE;
|
||||
}
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully loaded CRL file:");
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
|
||||
|
||||
infof(data, " CRLfile: %s", ssl_crlfile);
|
||||
}
|
||||
|
||||
if(verifypeer) {
|
||||
/* Try building a chain using issuers in the trusted store first to avoid
|
||||
problems with server-sent legacy intermediates. Newer versions of
|
||||
OpenSSL do alternate chain checking by default but we do not know how to
|
||||
determine that in a reliable manner.
|
||||
https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest
|
||||
*/
|
||||
#if defined(X509_V_FLAG_TRUSTED_FIRST)
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_TRUSTED_FIRST);
|
||||
#endif
|
||||
#ifdef X509_V_FLAG_PARTIAL_CHAIN
|
||||
if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) {
|
||||
/* Have intermediate certificates in the trust store be treated as
|
||||
trust-anchors, in the same way as self-signed root CA certificates
|
||||
are. This allows users to verify servers using the intermediate cert
|
||||
only, instead of needing the whole chain.
|
||||
|
||||
Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we
|
||||
cannot do partial chains with a CRL check.
|
||||
*/
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_PARTIAL_CHAIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* OpenSSL always tries to verify the peer, this only says whether it should
|
||||
* fail to connect if the verification fails, or if it should continue
|
||||
* anyway. In the latter case the result of the verification is checked with
|
||||
* SSL_get_verify_result() below. */
|
||||
SSL_CTX_set_verify(backend->ctx,
|
||||
verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
||||
|
||||
/* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
|
||||
#ifdef HAVE_KEYLOG_CALLBACK
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Enable the session cache because it's a prerequisite for the "new session"
|
||||
* callback. Use the "external storage" mode to prevent OpenSSL from creating
|
||||
* an internal session cache.
|
||||
*/
|
||||
SSL_CTX_set_session_cache_mode(backend->ctx,
|
||||
SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
|
||||
SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb);
|
||||
|
||||
/* give application a chance to interfere with SSL set up. */
|
||||
if(data->set.ssl.fsslctx) {
|
||||
Curl_set_in_callback(data, true);
|
||||
result = (*data->set.ssl.fsslctx)(data, backend->ctx,
|
||||
data->set.ssl.fsslctxp);
|
||||
Curl_set_in_callback(data, false);
|
||||
if(result) {
|
||||
failf(data, "error signaled by ssl ctx callback");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* Let's make an SSL structure */
|
||||
if(backend->handle)
|
||||
SSL_free(backend->handle);
|
||||
backend->handle = SSL_new(backend->ctx);
|
||||
if(!backend->handle) {
|
||||
failf(data, "SSL: couldn't create a context (handle)");
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus))
|
||||
SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG)
|
||||
SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely);
|
||||
#endif
|
||||
|
||||
SSL_set_connect_state(backend->handle);
|
||||
|
||||
backend->server_cert = 0x0;
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
|
||||
#ifdef ENABLE_IPV6
|
||||
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
|
||||
#endif
|
||||
sni) {
|
||||
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
|
||||
if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) {
|
||||
failf(data, "Failed set SNI");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!ossl_associate_connection(data, conn, sockindex)) {
|
||||
/* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */
|
||||
failf(data, "SSL: ossl_associate_connection failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(primary.sessionid)) {
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
|
||||
&ssl_sessionid, NULL, sockindex)) {
|
||||
/* we got a session id, use it! */
|
||||
if(!SSL_set_session(backend->handle, ssl_sessionid)) {
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
failf(data, "SSL: SSL_set_session failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
/* Informational message */
|
||||
infof(data, "SSL re-using session ID");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
BIO *const bio = BIO_new(BIO_f_ssl());
|
||||
struct ssl_backend_data *proxy_backend;
|
||||
SSL* handle = NULL;
|
||||
proxy_backend = conn->proxy_ssl[sockindex].backend;
|
||||
DEBUGASSERT(proxy_backend);
|
||||
handle = proxy_backend->handle;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(handle != NULL);
|
||||
DEBUGASSERT(bio != NULL);
|
||||
BIO_set_ssl(bio, handle, FALSE);
|
||||
SSL_set_bio(backend->handle, bio, bio);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!SSL_set_fd(backend->handle, (int)sockfd)) {
|
||||
/* pass the raw socket into the SSL layers */
|
||||
failf(data, "SSL: SSL_set_fd failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
/* Provenance: curl/curl ossl_connect_step1 (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* parent: 7e92d12b4e6911f424678a133b19de670e183a59
|
||||
* commit_url: https://github.com/curl/curl/commit/f18af4f874cecab82a9797e8c7541e0990c7a64c
|
||||
* cve: CVE-2022-27782
|
||||
* license: curl
|
||||
* function: ossl_connect_step1
|
||||
* relpath: lib/vtls/openssl.c
|
||||
*/
|
||||
|
||||
static CURLcode ossl_connect_step1(struct Curl_easy *data,
|
||||
struct connectdata *conn, int sockindex)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
char *ciphers;
|
||||
SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
|
||||
X509_LOOKUP *lookup = NULL;
|
||||
curl_socket_t sockfd = conn->sock[sockindex];
|
||||
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
|
||||
ctx_option_t ctx_options = 0;
|
||||
void *ssl_sessionid = NULL;
|
||||
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
bool sni;
|
||||
const char * const hostname = SSL_HOST_NAME();
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
struct in6_addr addr;
|
||||
#else
|
||||
struct in_addr addr;
|
||||
#endif
|
||||
#endif
|
||||
const long int ssl_version = SSL_CONN_CONFIG(version);
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(authtype);
|
||||
#endif
|
||||
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
|
||||
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
|
||||
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
|
||||
const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
|
||||
const char * const ssl_cafile =
|
||||
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
|
||||
(ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
|
||||
const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
|
||||
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
|
||||
const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile);
|
||||
char error_buffer[256];
|
||||
struct ssl_backend_data *backend = connssl->backend;
|
||||
bool imported_native_ca = false;
|
||||
|
||||
DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
|
||||
DEBUGASSERT(backend);
|
||||
|
||||
/* Make funny stuff to get random input */
|
||||
result = ossl_seed(data);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK;
|
||||
|
||||
/* check to see if we've been told to use an explicit SSL/TLS version */
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1:
|
||||
case CURL_SSLVERSION_TLSv1_0:
|
||||
case CURL_SSLVERSION_TLSv1_1:
|
||||
case CURL_SSLVERSION_TLSv1_2:
|
||||
case CURL_SSLVERSION_TLSv1_3:
|
||||
/* it will be handled later with the context options */
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
|
||||
req_method = TLS_client_method();
|
||||
#else
|
||||
req_method = SSLv23_client_method();
|
||||
#endif
|
||||
use_sni(TRUE);
|
||||
break;
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
failf(data, "No SSLv2 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
failf(data, "No SSLv3 support");
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
DEBUGASSERT(!backend->ctx);
|
||||
backend->ctx = SSL_CTX_new(req_method);
|
||||
|
||||
if(!backend->ctx) {
|
||||
failf(data, "SSL: couldn't create a context: %s",
|
||||
ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
|
||||
#ifdef SSL_CTRL_SET_MSG_CALLBACK
|
||||
if(data->set.fdebug && data->set.verbose) {
|
||||
/* the SSL trace callback is only used for verbose logging */
|
||||
SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
|
||||
SSL_CTX_set_msg_callback_arg(backend->ctx, conn);
|
||||
set_logger(conn, data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* OpenSSL contains code to work around lots of bugs and flaws in various
|
||||
SSL-implementations. SSL_CTX_set_options() is used to enabled those
|
||||
work-arounds. The man page for this option states that SSL_OP_ALL enables
|
||||
all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
|
||||
enable the bug workaround options if compatibility with somewhat broken
|
||||
implementations is desired."
|
||||
|
||||
The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to
|
||||
disable "rfc4507bis session ticket support". rfc4507bis was later turned
|
||||
into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077
|
||||
|
||||
The enabled extension concerns the session management. I wonder how often
|
||||
libcurl stops a connection and then resumes a TLS session. Also, sending
|
||||
the session data is some overhead. I suggest that you just use your
|
||||
proposed patch (which explicitly disables TICKET).
|
||||
|
||||
If someone writes an application with libcurl and OpenSSL who wants to
|
||||
enable the feature, one can do this in the SSL callback.
|
||||
|
||||
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
|
||||
interoperability with web server Netscape Enterprise Server 2.0.1 which
|
||||
was released back in 1996.
|
||||
|
||||
Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
|
||||
become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
|
||||
CVE-2010-4180 when using previous OpenSSL versions we no longer enable
|
||||
this option regardless of OpenSSL version and SSL_OP_ALL definition.
|
||||
|
||||
OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
|
||||
(https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
|
||||
SSL_OP_ALL that _disables_ that work-around despite the fact that
|
||||
SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
|
||||
keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
|
||||
must not be set.
|
||||
*/
|
||||
|
||||
ctx_options = SSL_OP_ALL;
|
||||
|
||||
#ifdef SSL_OP_NO_TICKET
|
||||
ctx_options |= SSL_OP_NO_TICKET;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NO_COMPRESSION
|
||||
ctx_options |= SSL_OP_NO_COMPRESSION;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
||||
/* mitigate CVE-2010-4180 */
|
||||
ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
|
||||
#endif
|
||||
|
||||
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
/* unless the user explicitly asks to allow the protocol vulnerability we
|
||||
use the work-around */
|
||||
if(!SSL_SET_OPTION(enable_beast))
|
||||
ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
||||
#endif
|
||||
|
||||
switch(ssl_version) {
|
||||
case CURL_SSLVERSION_SSLv2:
|
||||
case CURL_SSLVERSION_SSLv3:
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
|
||||
/* "--tlsv<x.y>" options mean TLS >= version <x.y> */
|
||||
case CURL_SSLVERSION_DEFAULT:
|
||||
case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
|
||||
case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
|
||||
case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
|
||||
case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
|
||||
/* asking for any TLS version as the minimum, means no SSL versions
|
||||
allowed */
|
||||
ctx_options |= SSL_OP_NO_SSLv2;
|
||||
ctx_options |= SSL_OP_NO_SSLv3;
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
|
||||
result = set_ssl_version_min_max(backend->ctx, conn);
|
||||
#else
|
||||
result = set_ssl_version_min_max_legacy(&ctx_options, data, conn,
|
||||
sockindex);
|
||||
#endif
|
||||
if(result != CURLE_OK)
|
||||
return result;
|
||||
break;
|
||||
|
||||
default:
|
||||
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(backend->ctx, ctx_options);
|
||||
|
||||
#ifdef HAS_NPN
|
||||
if(conn->bits.tls_enable_npn)
|
||||
SSL_CTX_set_next_proto_select_cb(backend->ctx, select_next_proto_cb, data);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ALPN
|
||||
if(conn->bits.tls_enable_alpn) {
|
||||
int cur = 0;
|
||||
unsigned char protocols[128];
|
||||
|
||||
#ifdef USE_HTTP2
|
||||
if(data->state.httpwant >= CURL_HTTP_VERSION_2
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
&& (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)
|
||||
#endif
|
||||
) {
|
||||
protocols[cur++] = ALPN_H2_LENGTH;
|
||||
|
||||
memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
|
||||
cur += ALPN_H2_LENGTH;
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
|
||||
}
|
||||
#endif
|
||||
|
||||
protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
|
||||
memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
|
||||
cur += ALPN_HTTP_1_1_LENGTH;
|
||||
infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
|
||||
|
||||
/* expects length prefixed preference ordered list of protocols in wire
|
||||
* format
|
||||
*/
|
||||
if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
|
||||
failf(data, "Error setting ALPN");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
|
||||
if(!result &&
|
||||
!cert_stuff(data, backend->ctx,
|
||||
ssl_cert, ssl_cert_blob, ssl_cert_type,
|
||||
SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
|
||||
SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd)))
|
||||
result = CURLE_SSL_CERTPROBLEM;
|
||||
if(result)
|
||||
/* failf() is already done in cert_stuff() */
|
||||
return result;
|
||||
}
|
||||
|
||||
ciphers = SSL_CONN_CONFIG(cipher_list);
|
||||
if(!ciphers)
|
||||
ciphers = (char *)DEFAULT_CIPHER_SELECTION;
|
||||
if(ciphers) {
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
|
||||
failf(data, "failed setting cipher list: %s", ciphers);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "Cipher selection: %s", ciphers);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
|
||||
{
|
||||
char *ciphers13 = SSL_CONN_CONFIG(cipher_list13);
|
||||
if(ciphers13) {
|
||||
if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) {
|
||||
failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
infof(data, "TLS 1.3 cipher selection: %s", ciphers13);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
|
||||
/* OpenSSL 1.1.1 requires clients to opt-in for PHA */
|
||||
SSL_CTX_set_post_handshake_auth(backend->ctx, 1);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SSL_CTX_SET_EC_CURVES
|
||||
{
|
||||
char *curves = SSL_CONN_CONFIG(curves);
|
||||
if(curves) {
|
||||
if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
|
||||
failf(data, "failed setting curves list: '%s'", curves);
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENSSL_SRP
|
||||
if((ssl_authtype == CURL_TLSAUTH_SRP) &&
|
||||
Curl_allow_auth_to_host(data)) {
|
||||
char * const ssl_username = SSL_SET_OPTION(username);
|
||||
|
||||
infof(data, "Using TLS-SRP username: %s", ssl_username);
|
||||
|
||||
if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) {
|
||||
failf(data, "Unable to set SRP user name");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CTX_set_srp_password(backend->ctx, SSL_SET_OPTION(password))) {
|
||||
failf(data, "failed setting SRP password");
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
if(!SSL_CONN_CONFIG(cipher_list)) {
|
||||
infof(data, "Setting cipher list SRP");
|
||||
|
||||
if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) {
|
||||
failf(data, "failed setting SRP cipher list");
|
||||
return CURLE_SSL_CIPHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(USE_WIN32_CRYPTO)
|
||||
/* Import certificates from the Windows root certificate store if requested.
|
||||
https://stackoverflow.com/questions/9507184/
|
||||
https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
|
||||
https://datatracker.ietf.org/doc/html/rfc5280 */
|
||||
if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) &&
|
||||
(SSL_SET_OPTION(native_ca_store))) {
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
|
||||
|
||||
if(hStore) {
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
/* The array of enhanced key usage OIDs will vary per certificate and is
|
||||
declared outside of the loop so that rather than malloc/free each
|
||||
iteration we can grow it with realloc, when necessary. */
|
||||
CERT_ENHKEY_USAGE *enhkey_usage = NULL;
|
||||
DWORD enhkey_usage_size = 0;
|
||||
|
||||
/* This loop makes a best effort to import all valid certificates from
|
||||
the MS root store. If a certificate cannot be imported it is skipped.
|
||||
'result' is used to store only hard-fail conditions (such as out of
|
||||
memory) that cause an early break. */
|
||||
result = CURLE_OK;
|
||||
for(;;) {
|
||||
X509 *x509;
|
||||
FILETIME now;
|
||||
BYTE key_usage[2];
|
||||
DWORD req_size;
|
||||
const unsigned char *encoded_cert;
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
char cert_name[256];
|
||||
#endif
|
||||
|
||||
pContext = CertEnumCertificatesInStore(hStore, pContext);
|
||||
if(!pContext)
|
||||
break;
|
||||
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
|
||||
NULL, cert_name, sizeof(cert_name))) {
|
||||
strcpy(cert_name, "Unknown");
|
||||
}
|
||||
infof(data, "SSL: Checking cert \"%s\"", cert_name);
|
||||
#endif
|
||||
|
||||
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
|
||||
if(!encoded_cert)
|
||||
continue;
|
||||
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
|
||||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
|
||||
continue;
|
||||
|
||||
/* If key usage exists check for signing attribute */
|
||||
if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
|
||||
pContext->pCertInfo,
|
||||
key_usage, sizeof(key_usage))) {
|
||||
if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
|
||||
continue;
|
||||
}
|
||||
else if(GetLastError())
|
||||
continue;
|
||||
|
||||
/* If enhanced key usage exists check for server auth attribute.
|
||||
*
|
||||
* Note "In a Microsoft environment, a certificate might also have EKU
|
||||
* extended properties that specify valid uses for the certificate."
|
||||
* The call below checks both, and behavior varies depending on what is
|
||||
* found. For more details see CertGetEnhancedKeyUsage doc.
|
||||
*/
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
|
||||
if(req_size && req_size > enhkey_usage_size) {
|
||||
void *tmp = realloc(enhkey_usage, req_size);
|
||||
|
||||
if(!tmp) {
|
||||
failf(data, "SSL: Out of memory allocating for OID list");
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
|
||||
enhkey_usage_size = req_size;
|
||||
}
|
||||
|
||||
if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
|
||||
if(!enhkey_usage->cUsageIdentifier) {
|
||||
/* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
|
||||
good for all uses. If it returns zero, the certificate has no
|
||||
valid uses." */
|
||||
if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
DWORD i;
|
||||
bool found = false;
|
||||
|
||||
for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
|
||||
if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
|
||||
enhkey_usage->rgpszUsageIdentifier[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if(!x509)
|
||||
continue;
|
||||
|
||||
/* Try to import the certificate. This may fail for legitimate reasons
|
||||
such as duplicate certificate, which is allowed by MS but not
|
||||
OpenSSL. */
|
||||
if(X509_STORE_add_cert(store, x509) == 1) {
|
||||
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
|
||||
infof(data, "SSL: Imported cert \"%s\"", cert_name);
|
||||
#endif
|
||||
imported_native_ca = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported Windows CA store");
|
||||
else
|
||||
infof(data, "error importing Windows CA store, continuing anyway");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(backend->ctx, ca_info_blob);
|
||||
if(result) {
|
||||
if(result == CURLE_OUT_OF_MEMORY ||
|
||||
(verifypeer && !imported_native_ca)) {
|
||||
failf(data, "error importing CA certificate blob");
|
||||
return result;
|
||||
}
|
||||
/* Only warn if no certificate verification is required. */
|
||||
infof(data, "error importing CA certificate blob, continuing anyway");
|
||||
}
|
||||
}
|
||||
|
||||
if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) {
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
if(ssl_cafile &&
|
||||
!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
if(ssl_capath &&
|
||||
!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
#else
|
||||
/* tell OpenSSL where to find CA certificates that are used to verify the
|
||||
server's certificate. */
|
||||
if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
#endif
|
||||
infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
if(verifypeer &&
|
||||
!ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built-in default as fallback */
|
||||
SSL_CTX_set_default_verify_paths(backend->ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ssl_crlfile) {
|
||||
/* tell OpenSSL where to find CRL file that is used to check certificate
|
||||
* revocation */
|
||||
lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_LOOKUP_file());
|
||||
if(!lookup ||
|
||||
(!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
|
||||
failf(data, "error loading CRL file: %s", ssl_crlfile);
|
||||
return CURLE_SSL_CRL_BADFILE;
|
||||
}
|
||||
/* Everything is fine. */
|
||||
infof(data, "successfully loaded CRL file:");
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
|
||||
|
||||
infof(data, " CRLfile: %s", ssl_crlfile);
|
||||
}
|
||||
|
||||
if(verifypeer) {
|
||||
/* Try building a chain using issuers in the trusted store first to avoid
|
||||
problems with server-sent legacy intermediates. Newer versions of
|
||||
OpenSSL do alternate chain checking by default but we do not know how to
|
||||
determine that in a reliable manner.
|
||||
https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest
|
||||
*/
|
||||
#if defined(X509_V_FLAG_TRUSTED_FIRST)
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_TRUSTED_FIRST);
|
||||
#endif
|
||||
#ifdef X509_V_FLAG_PARTIAL_CHAIN
|
||||
if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) {
|
||||
/* Have intermediate certificates in the trust store be treated as
|
||||
trust-anchors, in the same way as self-signed root CA certificates
|
||||
are. This allows users to verify servers using the intermediate cert
|
||||
only, instead of needing the whole chain.
|
||||
|
||||
Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we
|
||||
cannot do partial chains with a CRL check.
|
||||
*/
|
||||
X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx),
|
||||
X509_V_FLAG_PARTIAL_CHAIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* OpenSSL always tries to verify the peer, this only says whether it should
|
||||
* fail to connect if the verification fails, or if it should continue
|
||||
* anyway. In the latter case the result of the verification is checked with
|
||||
* SSL_get_verify_result() below. */
|
||||
SSL_CTX_set_verify(backend->ctx,
|
||||
verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
||||
|
||||
/* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
|
||||
#ifdef HAVE_KEYLOG_CALLBACK
|
||||
if(Curl_tls_keylog_enabled()) {
|
||||
SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Enable the session cache because it's a prerequisite for the "new session"
|
||||
* callback. Use the "external storage" mode to prevent OpenSSL from creating
|
||||
* an internal session cache.
|
||||
*/
|
||||
SSL_CTX_set_session_cache_mode(backend->ctx,
|
||||
SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
|
||||
SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb);
|
||||
|
||||
/* give application a chance to interfere with SSL set up. */
|
||||
if(data->set.ssl.fsslctx) {
|
||||
Curl_set_in_callback(data, true);
|
||||
result = (*data->set.ssl.fsslctx)(data, backend->ctx,
|
||||
data->set.ssl.fsslctxp);
|
||||
Curl_set_in_callback(data, false);
|
||||
if(result) {
|
||||
failf(data, "error signaled by ssl ctx callback");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* Let's make an SSL structure */
|
||||
if(backend->handle)
|
||||
SSL_free(backend->handle);
|
||||
backend->handle = SSL_new(backend->ctx);
|
||||
if(!backend->handle) {
|
||||
failf(data, "SSL: couldn't create a context (handle)");
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
|
||||
!defined(OPENSSL_NO_OCSP)
|
||||
if(SSL_CONN_CONFIG(verifystatus))
|
||||
SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp);
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG)
|
||||
SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely);
|
||||
#endif
|
||||
|
||||
SSL_set_connect_state(backend->handle);
|
||||
|
||||
backend->server_cert = 0x0;
|
||||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
|
||||
#ifdef ENABLE_IPV6
|
||||
(0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
|
||||
#endif
|
||||
sni) {
|
||||
char *snihost = Curl_ssl_snihost(data, hostname, NULL);
|
||||
if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) {
|
||||
failf(data, "Failed set SNI");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!ossl_associate_connection(data, conn, sockindex)) {
|
||||
/* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */
|
||||
failf(data, "SSL: ossl_associate_connection failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
if(SSL_SET_OPTION(primary.sessionid)) {
|
||||
Curl_ssl_sessionid_lock(data);
|
||||
if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE,
|
||||
&ssl_sessionid, NULL, sockindex)) {
|
||||
/* we got a session id, use it! */
|
||||
if(!SSL_set_session(backend->handle, ssl_sessionid)) {
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
failf(data, "SSL: SSL_set_session failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer,
|
||||
sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
/* Informational message */
|
||||
infof(data, "SSL re-using session ID");
|
||||
}
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(conn->proxy_ssl[sockindex].use) {
|
||||
BIO *const bio = BIO_new(BIO_f_ssl());
|
||||
struct ssl_backend_data *proxy_backend;
|
||||
SSL* handle = NULL;
|
||||
proxy_backend = conn->proxy_ssl[sockindex].backend;
|
||||
DEBUGASSERT(proxy_backend);
|
||||
handle = proxy_backend->handle;
|
||||
DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state);
|
||||
DEBUGASSERT(handle != NULL);
|
||||
DEBUGASSERT(bio != NULL);
|
||||
BIO_set_ssl(bio, handle, FALSE);
|
||||
SSL_set_bio(backend->handle, bio, bio);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if(!SSL_set_fd(backend->handle, (int)sockfd)) {
|
||||
/* pass the raw socket into the SSL layers */
|
||||
failf(data, "SSL: SSL_set_fd failed: %s",
|
||||
ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)));
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
connssl->connecting_state = ssl_connect_2;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Provenance: curl/curl hsts_create (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* parent: ff3ee510c328db03bf171cae6179bb9463fb054f
|
||||
* commit_url: https://github.com/curl/curl/commit/fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* cve: CVE-2022-30115
|
||||
* license: curl
|
||||
* function: hsts_create
|
||||
* relpath: lib/hsts.c
|
||||
*/
|
||||
|
||||
static CURLcode hsts_create(struct hsts *h,
|
||||
const char *hostname,
|
||||
bool subdomains,
|
||||
curl_off_t expires)
|
||||
{
|
||||
struct stsentry *sts = hsts_entry();
|
||||
char *duphost;
|
||||
size_t hlen;
|
||||
if(!sts)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
duphost = strdup(hostname);
|
||||
if(!duphost) {
|
||||
free(sts);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
hlen = strlen(duphost);
|
||||
if(duphost[hlen - 1] == '.')
|
||||
/* strip off trailing any dot */
|
||||
duphost[--hlen] = 0;
|
||||
|
||||
sts->host = duphost;
|
||||
sts->expires = expires;
|
||||
sts->includeSubDomains = subdomains;
|
||||
Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node);
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Provenance: curl/curl hsts_create (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: ff3ee510c328db03bf171cae6179bb9463fb054f
|
||||
* parent: ff3ee510c328db03bf171cae6179bb9463fb054f
|
||||
* commit_url: https://github.com/curl/curl/commit/fae6fea209a2d4db1582f608bd8cc8000721733a
|
||||
* cve: CVE-2022-30115
|
||||
* license: curl
|
||||
* function: hsts_create
|
||||
* relpath: lib/hsts.c
|
||||
*/
|
||||
|
||||
static CURLcode hsts_create(struct hsts *h,
|
||||
const char *hostname,
|
||||
bool subdomains,
|
||||
curl_off_t expires)
|
||||
{
|
||||
struct stsentry *sts = hsts_entry();
|
||||
if(!sts)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
sts->expires = expires;
|
||||
sts->includeSubDomains = subdomains;
|
||||
sts->host = strdup(hostname);
|
||||
if(!sts->host) {
|
||||
free(sts);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node);
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Provenance: curl/curl Curl_build_unencoding_stack (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 3a09fbb7f264c67c438d01a30669ce325aa508e2
|
||||
* parent: 6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* commit_url: https://github.com/curl/curl/commit/3a09fbb7f264c67c438d01a30669ce325aa508e2
|
||||
* cve: CVE-2022-32206
|
||||
* license: curl
|
||||
* function: Curl_build_unencoding_stack
|
||||
* relpath: lib/content_encoding.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
|
||||
const char *enclist, int maybechunked)
|
||||
{
|
||||
struct SingleRequest *k = &data->req;
|
||||
int counter = 0;
|
||||
|
||||
do {
|
||||
const char *name;
|
||||
size_t namelen;
|
||||
|
||||
/* Parse a single encoding name. */
|
||||
while(ISSPACE(*enclist) || *enclist == ',')
|
||||
enclist++;
|
||||
|
||||
name = enclist;
|
||||
|
||||
for(namelen = 0; *enclist && *enclist != ','; enclist++)
|
||||
if(!ISSPACE(*enclist))
|
||||
namelen = enclist - name + 1;
|
||||
|
||||
/* Special case: chunked encoding is handled at the reader level. */
|
||||
if(maybechunked && namelen == 7 && strncasecompare(name, "chunked", 7)) {
|
||||
k->chunk = TRUE; /* chunks coming our way. */
|
||||
Curl_httpchunk_init(data); /* init our chunky engine. */
|
||||
}
|
||||
else if(namelen) {
|
||||
const struct content_encoding *encoding = find_encoding(name, namelen);
|
||||
struct contenc_writer *writer;
|
||||
|
||||
if(!k->writer_stack) {
|
||||
k->writer_stack = new_unencoding_writer(data, &client_encoding, NULL);
|
||||
|
||||
if(!k->writer_stack)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if(!encoding)
|
||||
encoding = &error_encoding; /* Defer error at stack use. */
|
||||
|
||||
if(++counter >= MAX_ENCODE_STACK) {
|
||||
failf(data, "Reject response due to %u content encodings",
|
||||
counter);
|
||||
return CURLE_BAD_CONTENT_ENCODING;
|
||||
}
|
||||
/* Stack the unencoding stage. */
|
||||
writer = new_unencoding_writer(data, encoding, k->writer_stack);
|
||||
if(!writer)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
k->writer_stack = writer;
|
||||
}
|
||||
} while(*enclist);
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* Provenance: curl/curl Curl_build_unencoding_stack (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* parent: 6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* commit_url: https://github.com/curl/curl/commit/3a09fbb7f264c67c438d01a30669ce325aa508e2
|
||||
* cve: CVE-2022-32206
|
||||
* license: curl
|
||||
* function: Curl_build_unencoding_stack
|
||||
* relpath: lib/content_encoding.c
|
||||
*/
|
||||
|
||||
CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
|
||||
const char *enclist, int maybechunked)
|
||||
{
|
||||
struct SingleRequest *k = &data->req;
|
||||
|
||||
do {
|
||||
const char *name;
|
||||
size_t namelen;
|
||||
|
||||
/* Parse a single encoding name. */
|
||||
while(ISSPACE(*enclist) || *enclist == ',')
|
||||
enclist++;
|
||||
|
||||
name = enclist;
|
||||
|
||||
for(namelen = 0; *enclist && *enclist != ','; enclist++)
|
||||
if(!ISSPACE(*enclist))
|
||||
namelen = enclist - name + 1;
|
||||
|
||||
/* Special case: chunked encoding is handled at the reader level. */
|
||||
if(maybechunked && namelen == 7 && strncasecompare(name, "chunked", 7)) {
|
||||
k->chunk = TRUE; /* chunks coming our way. */
|
||||
Curl_httpchunk_init(data); /* init our chunky engine. */
|
||||
}
|
||||
else if(namelen) {
|
||||
const struct content_encoding *encoding = find_encoding(name, namelen);
|
||||
struct contenc_writer *writer;
|
||||
|
||||
if(!k->writer_stack) {
|
||||
k->writer_stack = new_unencoding_writer(data, &client_encoding, NULL);
|
||||
|
||||
if(!k->writer_stack)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if(!encoding)
|
||||
encoding = &error_encoding; /* Defer error at stack use. */
|
||||
|
||||
/* Stack the unencoding stage. */
|
||||
writer = new_unencoding_writer(data, encoding, k->writer_stack);
|
||||
if(!writer)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
k->writer_stack = writer;
|
||||
}
|
||||
} while(*enclist);
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/* Provenance: curl/curl cookie_output (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 20f9dd6bae50b7223171b17ba7798946e74f877f
|
||||
* parent: 46f8911d3942dc06fdd67e9f6f3908982e5d2fb4
|
||||
* commit_url: https://github.com/curl/curl/commit/20f9dd6bae50b7223171b17ba7798946e74f877f
|
||||
* cve: CVE-2022-32207
|
||||
* license: curl
|
||||
* function: cookie_output
|
||||
* relpath: lib/cookie.c
|
||||
*/
|
||||
|
||||
* cookie_output()
|
||||
*
|
||||
* Writes all internally known cookies to the specified file. Specify
|
||||
* "-" as file name to write to stdout.
|
||||
*
|
||||
* The function returns non-zero on write failure.
|
||||
*/
|
||||
static CURLcode cookie_output(struct Curl_easy *data,
|
||||
struct CookieInfo *c, const char *filename)
|
||||
{
|
||||
struct Cookie *co;
|
||||
FILE *out = NULL;
|
||||
bool use_stdout = FALSE;
|
||||
char *tempstore = NULL;
|
||||
CURLcode error = CURLE_OK;
|
||||
|
||||
if(!c)
|
||||
/* no cookie engine alive */
|
||||
return CURLE_OK;
|
||||
|
||||
/* at first, remove expired cookies */
|
||||
remove_expired(c);
|
||||
|
||||
if(!strcmp("-", filename)) {
|
||||
/* use stdout */
|
||||
out = stdout;
|
||||
use_stdout = TRUE;
|
||||
}
|
||||
else {
|
||||
error = Curl_fopen(data, filename, &out, &tempstore);
|
||||
if(error)
|
||||
goto error;
|
||||
}
|
||||
|
||||
fputs("# Netscape HTTP Cookie File\n"
|
||||
"# https://curl.se/docs/http-cookies.html\n"
|
||||
"# This file was generated by libcurl! Edit at your own risk.\n\n",
|
||||
out);
|
||||
|
||||
if(c->numcookies) {
|
||||
unsigned int i;
|
||||
size_t nvalid = 0;
|
||||
struct Cookie **array;
|
||||
|
||||
array = calloc(1, sizeof(struct Cookie *) * c->numcookies);
|
||||
if(!array) {
|
||||
error = CURLE_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* only sort the cookies with a domain property */
|
||||
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
|
||||
for(co = c->cookies[i]; co; co = co->next) {
|
||||
if(!co->domain)
|
||||
continue;
|
||||
array[nvalid++] = co;
|
||||
}
|
||||
}
|
||||
|
||||
qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
|
||||
|
||||
for(i = 0; i < nvalid; i++) {
|
||||
char *format_ptr = get_netscape_format(array[i]);
|
||||
if(!format_ptr) {
|
||||
free(array);
|
||||
error = CURLE_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
fprintf(out, "%s\n", format_ptr);
|
||||
free(format_ptr);
|
||||
}
|
||||
|
||||
free(array);
|
||||
}
|
||||
|
||||
if(!use_stdout) {
|
||||
fclose(out);
|
||||
out = NULL;
|
||||
if(tempstore && Curl_rename(tempstore, filename)) {
|
||||
unlink(tempstore);
|
||||
error = CURLE_WRITE_ERROR;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we reach here we have successfully written a cookie file so theree is
|
||||
* no need to inspect the error, any error case should have jumped into the
|
||||
* error block below.
|
||||
*/
|
||||
free(tempstore);
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
if(out && !use_stdout)
|
||||
fclose(out);
|
||||
free(tempstore);
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Provenance: curl/curl cookie_output (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 46f8911d3942dc06fdd67e9f6f3908982e5d2fb4
|
||||
* parent: 46f8911d3942dc06fdd67e9f6f3908982e5d2fb4
|
||||
* commit_url: https://github.com/curl/curl/commit/20f9dd6bae50b7223171b17ba7798946e74f877f
|
||||
* cve: CVE-2022-32207
|
||||
* license: curl
|
||||
* function: cookie_output
|
||||
* relpath: lib/cookie.c
|
||||
*/
|
||||
|
||||
* cookie_output()
|
||||
*
|
||||
* Writes all internally known cookies to the specified file. Specify
|
||||
* "-" as file name to write to stdout.
|
||||
*
|
||||
* The function returns non-zero on write failure.
|
||||
*/
|
||||
static CURLcode cookie_output(struct Curl_easy *data,
|
||||
struct CookieInfo *c, const char *filename)
|
||||
{
|
||||
struct Cookie *co;
|
||||
FILE *out = NULL;
|
||||
bool use_stdout = FALSE;
|
||||
char *tempstore = NULL;
|
||||
CURLcode error = CURLE_OK;
|
||||
|
||||
if(!c)
|
||||
/* no cookie engine alive */
|
||||
return CURLE_OK;
|
||||
|
||||
/* at first, remove expired cookies */
|
||||
remove_expired(c);
|
||||
|
||||
if(!strcmp("-", filename)) {
|
||||
/* use stdout */
|
||||
out = stdout;
|
||||
use_stdout = TRUE;
|
||||
}
|
||||
else {
|
||||
unsigned char randsuffix[9];
|
||||
|
||||
if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix)))
|
||||
return 2;
|
||||
|
||||
tempstore = aprintf("%s.%s.tmp", filename, randsuffix);
|
||||
if(!tempstore)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
out = fopen(tempstore, FOPEN_WRITETEXT);
|
||||
if(!out) {
|
||||
error = CURLE_WRITE_ERROR;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
fputs("# Netscape HTTP Cookie File\n"
|
||||
"# https://curl.se/docs/http-cookies.html\n"
|
||||
"# This file was generated by libcurl! Edit at your own risk.\n\n",
|
||||
out);
|
||||
|
||||
if(c->numcookies) {
|
||||
unsigned int i;
|
||||
size_t nvalid = 0;
|
||||
struct Cookie **array;
|
||||
|
||||
array = calloc(1, sizeof(struct Cookie *) * c->numcookies);
|
||||
if(!array) {
|
||||
error = CURLE_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* only sort the cookies with a domain property */
|
||||
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
|
||||
for(co = c->cookies[i]; co; co = co->next) {
|
||||
if(!co->domain)
|
||||
continue;
|
||||
array[nvalid++] = co;
|
||||
}
|
||||
}
|
||||
|
||||
qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
|
||||
|
||||
for(i = 0; i < nvalid; i++) {
|
||||
char *format_ptr = get_netscape_format(array[i]);
|
||||
if(!format_ptr) {
|
||||
free(array);
|
||||
error = CURLE_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
fprintf(out, "%s\n", format_ptr);
|
||||
free(format_ptr);
|
||||
}
|
||||
|
||||
free(array);
|
||||
}
|
||||
|
||||
if(!use_stdout) {
|
||||
fclose(out);
|
||||
out = NULL;
|
||||
if(Curl_rename(tempstore, filename)) {
|
||||
unlink(tempstore);
|
||||
error = CURLE_WRITE_ERROR;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we reach here we have successfully written a cookie file so theree is
|
||||
* no need to inspect the error, any error case should have jumped into the
|
||||
* error block below.
|
||||
*/
|
||||
free(tempstore);
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
if(out && !use_stdout)
|
||||
fclose(out);
|
||||
free(tempstore);
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* Provenance: curl/curl read_data (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: 6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* parent: 2b67a0a1129b20a56ed04859fec8999f5c0c4280
|
||||
* commit_url: https://github.com/curl/curl/commit/6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* cve: CVE-2022-32208
|
||||
* license: curl
|
||||
* function: read_data
|
||||
* relpath: lib/krb5.c
|
||||
*/
|
||||
|
||||
static CURLcode read_data(struct connectdata *conn,
|
||||
curl_socket_t fd,
|
||||
struct krb5buffer *buf)
|
||||
{
|
||||
int len;
|
||||
CURLcode result;
|
||||
int nread;
|
||||
|
||||
result = socket_read(fd, &len, sizeof(len));
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(len) {
|
||||
/* only realloc if there was a length */
|
||||
len = ntohl(len);
|
||||
if(len > CURL_MAX_INPUT_LENGTH)
|
||||
len = 0;
|
||||
else
|
||||
buf->data = Curl_saferealloc(buf->data, len);
|
||||
}
|
||||
if(!len || !buf->data)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
result = socket_read(fd, buf->data, len);
|
||||
if(result)
|
||||
return result;
|
||||
nread = conn->mech->decode(conn->app_data, buf->data, len,
|
||||
conn->data_prot, conn);
|
||||
if(nread < 0)
|
||||
return CURLE_RECV_ERROR;
|
||||
buf->size = (size_t)nread;
|
||||
buf->index = 0;
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* Provenance: curl/curl read_data (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 2b67a0a1129b20a56ed04859fec8999f5c0c4280
|
||||
* parent: 2b67a0a1129b20a56ed04859fec8999f5c0c4280
|
||||
* commit_url: https://github.com/curl/curl/commit/6ecdf5136b52af747e7bda08db9a748256b1cd09
|
||||
* cve: CVE-2022-32208
|
||||
* license: curl
|
||||
* function: read_data
|
||||
* relpath: lib/krb5.c
|
||||
*/
|
||||
|
||||
static CURLcode read_data(struct connectdata *conn,
|
||||
curl_socket_t fd,
|
||||
struct krb5buffer *buf)
|
||||
{
|
||||
int len;
|
||||
CURLcode result;
|
||||
|
||||
result = socket_read(fd, &len, sizeof(len));
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(len) {
|
||||
/* only realloc if there was a length */
|
||||
len = ntohl(len);
|
||||
buf->data = Curl_saferealloc(buf->data, len);
|
||||
}
|
||||
if(!len || !buf->data)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
result = socket_read(fd, buf->data, len);
|
||||
if(result)
|
||||
return result;
|
||||
buf->size = conn->mech->decode(conn->app_data, buf->data, len,
|
||||
conn->data_prot, conn);
|
||||
buf->index = 0;
|
||||
return CURLE_OK;
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/* 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
|
||||
*/
|
||||
|
||||
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! */
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/* Provenance: curl/curl do_SOCKS5 (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 09e25b9d94f4106eac8ca3a43b221bdc66f405e4
|
||||
* parent: 09e25b9d94f4106eac8ca3a43b221bdc66f405e4
|
||||
* commit_url: https://github.com/curl/curl/commit/fb4415d8aee6c1045be932a34fe6107c2f5ed147
|
||||
* cve: CVE-2023-38545
|
||||
* license: curl
|
||||
* function: do_SOCKS5
|
||||
* relpath: lib/socks.c
|
||||
*/
|
||||
|
||||
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) {
|
||||
infof(data, "SOCKS5: server resolving disabled for hostnames of "
|
||||
"length > 255 [actual len=%zu]", hostname_len);
|
||||
socks5_resolve_local = TRUE;
|
||||
}
|
||||
|
||||
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++] = (char) hostname_len; /* one byte address 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! */
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/* 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
|
||||
*/
|
||||
|
||||
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! */
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/* Provenance: curl/curl do_SOCKS5 (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: 09e25b9d94f4106eac8ca3a43b221bdc66f405e4
|
||||
* parent: 09e25b9d94f4106eac8ca3a43b221bdc66f405e4
|
||||
* commit_url: https://github.com/curl/curl/commit/fb4415d8aee6c1045be932a34fe6107c2f5ed147
|
||||
* cve: CVE-2023-38545
|
||||
* license: curl
|
||||
* function: do_SOCKS5
|
||||
* relpath: lib/socks.c
|
||||
*/
|
||||
|
||||
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) {
|
||||
infof(data, "SOCKS5: server resolving disabled for hostnames of "
|
||||
"length > 255 [actual len=%zu]", hostname_len);
|
||||
socks5_resolve_local = TRUE;
|
||||
}
|
||||
|
||||
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++] = (char) hostname_len; /* one byte address 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! */
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Provenance: curl/curl mev_forget_socket (fixed).
|
||||
* repo: curl/curl
|
||||
* commit: e9a2b0dc72dd3a9aa3a5210e89079a3b719475dc
|
||||
* parent: f746780a1e548b4b93790b58d70c3b22fde5360d
|
||||
* commit_url: https://github.com/curl/curl/commit/e9a2b0dc72dd3a9aa3a5210e89079a3b719475dc
|
||||
* cve: CVE-2026-9080
|
||||
* license: curl
|
||||
* function: mev_forget_socket
|
||||
* relpath: lib/multi_ev.c
|
||||
*/
|
||||
|
||||
static CURLMcode mev_forget_socket(struct Curl_multi *multi,
|
||||
struct Curl_easy *data,
|
||||
curl_socket_t s,
|
||||
const char *cause)
|
||||
{
|
||||
struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
|
||||
int rc = 0;
|
||||
|
||||
if(!entry) /* we never knew or already forgot about this socket */
|
||||
return CURLM_OK;
|
||||
|
||||
/* We managed this socket before, tell the socket callback to forget it. */
|
||||
if(entry->announced && multi->socket_cb) {
|
||||
NOVERBOSE((void)cause);
|
||||
CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", cause, s);
|
||||
mev_in_callback(multi, TRUE);
|
||||
rc = multi->socket_cb(data, s, CURL_POLL_REMOVE,
|
||||
multi->socket_userp, entry->user_data);
|
||||
mev_in_callback(multi, FALSE);
|
||||
/* curl_easy_pause() is documented as callable from any callback; it
|
||||
* re-enters mev_assess() which may free this 'entry'. Re-fetch. */
|
||||
entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
|
||||
if(entry)
|
||||
entry->announced = FALSE;
|
||||
}
|
||||
|
||||
mev_sh_entry_kill(multi, s);
|
||||
if(rc == -1) {
|
||||
multi->dead = TRUE;
|
||||
return CURLM_ABORTED_BY_CALLBACK;
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/* Provenance: curl/curl mev_forget_socket (vuln).
|
||||
* repo: curl/curl
|
||||
* commit: f746780a1e548b4b93790b58d70c3b22fde5360d
|
||||
* parent: f746780a1e548b4b93790b58d70c3b22fde5360d
|
||||
* commit_url: https://github.com/curl/curl/commit/e9a2b0dc72dd3a9aa3a5210e89079a3b719475dc
|
||||
* cve: CVE-2026-9080
|
||||
* license: curl
|
||||
* function: mev_forget_socket
|
||||
* relpath: lib/multi_ev.c
|
||||
*/
|
||||
|
||||
static CURLMcode mev_forget_socket(struct Curl_multi *multi,
|
||||
struct Curl_easy *data,
|
||||
curl_socket_t s,
|
||||
const char *cause)
|
||||
{
|
||||
struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
|
||||
int rc = 0;
|
||||
|
||||
if(!entry) /* we never knew or already forgot about this socket */
|
||||
return CURLM_OK;
|
||||
|
||||
/* We managed this socket before, tell the socket callback to forget it. */
|
||||
if(entry->announced && multi->socket_cb) {
|
||||
NOVERBOSE((void)cause);
|
||||
CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", cause, s);
|
||||
mev_in_callback(multi, TRUE);
|
||||
rc = multi->socket_cb(data, s, CURL_POLL_REMOVE,
|
||||
multi->socket_userp, entry->user_data);
|
||||
mev_in_callback(multi, FALSE);
|
||||
entry->announced = FALSE;
|
||||
}
|
||||
|
||||
mev_sh_entry_kill(multi, s);
|
||||
if(rc == -1) {
|
||||
multi->dead = TRUE;
|
||||
return CURLM_ABORTED_BY_CALLBACK;
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* Provenance: mozilla-firefox/firefox mar_read_index (fixed).
|
||||
* repo: mozilla-firefox/firefox
|
||||
* commit: e46f28da1cd3a5d46a2680b3d87361e9e8de4ae6
|
||||
* parent: eed0931f85474d0db6e2326dfd9718c6bf1cd418
|
||||
* commit_url: https://github.com/mozilla-firefox/firefox/commit/e46f28da1cd3a5d46a2680b3d87361e9e8de4ae6
|
||||
* cve: BUG-2058173
|
||||
* license: MPL-2.0
|
||||
* function: mar_read_index
|
||||
* relpath: modules/libmar/src/mar_read.c
|
||||
*/
|
||||
|
||||
static int mar_read_index(MarFile* mar) {
|
||||
char id[MAR_ID_SIZE], *buf, *bufptr, *bufend;
|
||||
uint32_t offset_to_index, size_of_index;
|
||||
size_t mar_position = 0;
|
||||
|
||||
/* verify MAR ID */
|
||||
if (mar_read_buffer(mar, id, &mar_position, MAR_ID_SIZE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mar_read_buffer(mar, &offset_to_index, &mar_position, sizeof(uint32_t)) !=
|
||||
0) {
|
||||
return -1;
|
||||
}
|
||||
offset_to_index = ntohl(offset_to_index);
|
||||
|
||||
mar_position = 0;
|
||||
if (mar_buffer_seek(mar, &mar_position, offset_to_index) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (mar_read_buffer(mar, &size_of_index, &mar_position, sizeof(uint32_t)) !=
|
||||
0) {
|
||||
return -1;
|
||||
}
|
||||
size_of_index = ntohl(size_of_index);
|
||||
|
||||
if (size_of_index > mar->data_len) {
|
||||
return -1;
|
||||
}
|
||||
buf = (char*)malloc(size_of_index);
|
||||
if (!buf) {
|
||||
return -1;
|
||||
}
|
||||
if (mar_read_buffer(mar, buf, &mar_position, size_of_index) != 0) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bufptr = buf;
|
||||
bufend = buf + size_of_index;
|
||||
while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0);
|
||||
|
||||
free(buf);
|
||||
return (bufptr == bufend) ? 0 : -1;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Provenance: mozilla-firefox/firefox mar_read_index (vuln).
|
||||
* repo: mozilla-firefox/firefox
|
||||
* commit: eed0931f85474d0db6e2326dfd9718c6bf1cd418
|
||||
* parent: eed0931f85474d0db6e2326dfd9718c6bf1cd418
|
||||
* commit_url: https://github.com/mozilla-firefox/firefox/commit/e46f28da1cd3a5d46a2680b3d87361e9e8de4ae6
|
||||
* cve: BUG-2058173
|
||||
* license: MPL-2.0
|
||||
* function: mar_read_index
|
||||
* relpath: modules/libmar/src/mar_read.c
|
||||
*/
|
||||
|
||||
static int mar_read_index(MarFile* mar) {
|
||||
char id[MAR_ID_SIZE], *buf, *bufptr, *bufend;
|
||||
uint32_t offset_to_index, size_of_index;
|
||||
size_t mar_position = 0;
|
||||
|
||||
/* verify MAR ID */
|
||||
if (mar_read_buffer(mar, id, &mar_position, MAR_ID_SIZE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(id, MAR_ID, MAR_ID_SIZE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mar_read_buffer(mar, &offset_to_index, &mar_position, sizeof(uint32_t)) !=
|
||||
0) {
|
||||
return -1;
|
||||
}
|
||||
offset_to_index = ntohl(offset_to_index);
|
||||
|
||||
mar_position = 0;
|
||||
if (mar_buffer_seek(mar, &mar_position, offset_to_index) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (mar_read_buffer(mar, &size_of_index, &mar_position, sizeof(uint32_t)) !=
|
||||
0) {
|
||||
return -1;
|
||||
}
|
||||
size_of_index = ntohl(size_of_index);
|
||||
|
||||
buf = (char*)malloc(size_of_index);
|
||||
if (!buf) {
|
||||
return -1;
|
||||
}
|
||||
if (mar_read_buffer(mar, buf, &mar_position, size_of_index) != 0) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bufptr = buf;
|
||||
bufend = buf + size_of_index;
|
||||
while (bufptr < bufend && mar_consume_index(mar, &bufptr, bufend) == 0);
|
||||
|
||||
free(buf);
|
||||
return (bufptr == bufend) ? 0 : -1;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* Provenance: Firefox libmar mar_insert_item after CVE-2020-15667.
|
||||
* repo: mozilla-central
|
||||
* commit: b79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* parent: 0c0f777161a9499dd149853ff62d356f75d16c2a
|
||||
* commit_url: https://hg.mozilla.org/mozilla-central/rev/b79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* phabricator: https://phabricator.services.mozilla.com/rMOZILLACENTRALb79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* cve: CVE-2020-15667
|
||||
* bug: 1653371
|
||||
* license: MPL-2.0
|
||||
* function: mar_insert_item
|
||||
* relpath: modules/libmar/src/mar_read.c
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct MarItem_ {
|
||||
struct MarItem_ *next;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
uint32_t flags;
|
||||
char name[1];
|
||||
} MarItem;
|
||||
|
||||
typedef struct {
|
||||
MarItem *item_table[256];
|
||||
} MarFile;
|
||||
|
||||
static uint32_t mar_hash_name(const char *name) {
|
||||
(void)name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mar_insert_item(MarFile *mar, const char *name, uint32_t namelen, uint32_t offset, uint32_t length, uint32_t flags) {
|
||||
MarItem *item, *root;
|
||||
uint32_t hash;
|
||||
|
||||
item = (MarItem *)malloc(sizeof(MarItem) + namelen);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
item->next = NULL;
|
||||
item->offset = offset;
|
||||
item->length = length;
|
||||
item->flags = flags;
|
||||
memcpy(item->name, name, namelen + 1);
|
||||
|
||||
hash = mar_hash_name(name);
|
||||
|
||||
root = mar->item_table[hash];
|
||||
if (!root) {
|
||||
mar->item_table[hash] = item;
|
||||
} else {
|
||||
/* append item */
|
||||
while (root->next) root = root->next;
|
||||
root->next = item;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Provenance: Firefox libmar mar_insert_item before CVE-2020-15667.
|
||||
* repo: mozilla-central
|
||||
* commit: 0c0f777161a9499dd149853ff62d356f75d16c2a
|
||||
* parent: 0c0f777161a9499dd149853ff62d356f75d16c2a
|
||||
* fix: b79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* commit_url: https://hg.mozilla.org/mozilla-central/rev/b79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* phabricator: https://phabricator.services.mozilla.com/rMOZILLACENTRALb79b6cc78248eea7fda10bfb76aa273c19c9fa65
|
||||
* cve: CVE-2020-15667
|
||||
* bug: 1653371
|
||||
* license: MPL-2.0
|
||||
* function: mar_insert_item
|
||||
* relpath: modules/libmar/src/mar_read.c
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct MarItem_ {
|
||||
struct MarItem_ *next;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
uint32_t flags;
|
||||
char name[1];
|
||||
} MarItem;
|
||||
|
||||
typedef struct {
|
||||
MarItem *item_table[256];
|
||||
} MarFile;
|
||||
|
||||
static uint32_t mar_hash_name(const char *name) {
|
||||
(void)name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mar_insert_item(MarFile *mar, const char *name, int namelen, uint32_t offset, uint32_t length, uint32_t flags) {
|
||||
MarItem *item, *root;
|
||||
uint32_t hash;
|
||||
|
||||
item = (MarItem *)malloc(sizeof(MarItem) + namelen);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
item->next = NULL;
|
||||
item->offset = offset;
|
||||
item->length = length;
|
||||
item->flags = flags;
|
||||
memcpy(item->name, name, namelen + 1);
|
||||
|
||||
hash = mar_hash_name(name);
|
||||
|
||||
root = mar->item_table[hash];
|
||||
if (!root) {
|
||||
mar->item_table[hash] = item;
|
||||
} else {
|
||||
/* append item */
|
||||
while (root->next) root = root->next;
|
||||
root->next = item;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rebuild catalog.toml and training/manifest.jsonl from recipes.toml + excerpts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
RECIPES = ROOT / "recipes.toml"
|
||||
CATALOG = ROOT / "catalog.toml"
|
||||
TRAINING = ROOT / "training" / "manifest.jsonl"
|
||||
|
||||
LANG_TO_PAIR = {"c": "c_cpp", "cpp": "c_cpp", "perl": "unknown", "javascript": "javascript", "python": "python", "java": "java"}
|
||||
|
||||
|
||||
def excerpt_rel(recipe: dict[str, object]) -> tuple[str, str]:
|
||||
name = str(recipe["name"])
|
||||
ext = Path(str(recipe.get("path", "a.c"))).suffix or ".c"
|
||||
return f"{name}/vuln{ext}", f"{name}/fixed{ext}"
|
||||
|
||||
|
||||
def catalog_text(recipes: list[dict[str, object]]) -> str:
|
||||
lines = [
|
||||
"# Real-world VFC slice. Reviewed isolated functions.",
|
||||
"# Honesty dashboard — not the local 100% CI gate.",
|
||||
"# Do not add these names to LANGUAGE_MANIFESTS.",
|
||||
"# Regenerated by generate_catalog.py from recipes.toml.",
|
||||
"",
|
||||
]
|
||||
for recipe in recipes:
|
||||
vuln, fixed = excerpt_rel(recipe)
|
||||
lang = LANG_TO_PAIR.get(str(recipe.get("language", "c")), "c_cpp")
|
||||
lines += [
|
||||
"[[pair]]",
|
||||
f'name = "{recipe["name"]}"',
|
||||
'slice = "vfc"',
|
||||
f'language = "{lang}"',
|
||||
f'origin = "{recipe.get("origin", "")}"',
|
||||
f'license = "{recipe.get("license", "")}"',
|
||||
f'repo = "{recipe.get("repo", "")}"',
|
||||
f'cve = "{recipe.get("cve", "")}"',
|
||||
f'cwe = "{recipe.get("cwe", "")}"',
|
||||
f'commit = "{recipe.get("commit", "")}"',
|
||||
f'parent = "{recipe.get("parent", "")}"',
|
||||
f'commit_url = "{recipe.get("commit_url", "")}"',
|
||||
f'vuln = "{vuln}"',
|
||||
f'fixed = "{fixed}"',
|
||||
f'relpath = "{recipe.get("relpath", recipe.get("path", ""))}"',
|
||||
"min_recall = 1.0",
|
||||
'fix_policy = "silent"',
|
||||
"",
|
||||
"[[pair.expected]]",
|
||||
f'cwe = "{recipe.get("cwe", "")}"',
|
||||
f'class = "{recipe.get("class", "")}"',
|
||||
f'path = "{recipe.get("relpath", recipe.get("path", ""))}"',
|
||||
]
|
||||
if recipe.get("rule_id"):
|
||||
lines.append(f'rule_id = "{recipe["rule_id"]}"')
|
||||
if recipe.get("sink"):
|
||||
lines.append(f'sink = "{recipe["sink"]}"')
|
||||
if recipe.get("evidence"):
|
||||
evidence = str(recipe["evidence"]).replace('"', '\\"')
|
||||
lines.append(f'evidence = "{evidence}"')
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def training_rows(recipes: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||
rows: list[dict[str, object]] = []
|
||||
for recipe in recipes:
|
||||
vuln, fixed = excerpt_rel(recipe)
|
||||
base = {
|
||||
"pair": recipe["name"],
|
||||
"cve": recipe.get("cve", ""),
|
||||
"year": int(recipe.get("year", 0) or 0),
|
||||
"cwe": recipe.get("cwe", ""),
|
||||
"class": recipe.get("class", ""),
|
||||
"project": recipe.get("project", recipe.get("origin", "")),
|
||||
"repo": recipe.get("repo", ""),
|
||||
"license": recipe.get("license", ""),
|
||||
"function": recipe.get("function", ""),
|
||||
"relpath": recipe.get("relpath", recipe.get("path", "")),
|
||||
"commit_url": recipe.get("commit_url", ""),
|
||||
}
|
||||
rows.append({**base, "id": f"{recipe['name']}:vuln", "label": "vuln", "file": vuln, "commit": recipe.get("parent", "")})
|
||||
rows.append({**base, "id": f"{recipe['name']}:fixed", "label": "fixed", "file": fixed, "commit": recipe.get("commit", "")})
|
||||
return rows
|
||||
|
||||
|
||||
def main() -> int:
|
||||
recipes = list(tomllib.loads(RECIPES.read_text()).get("recipe", []))
|
||||
CATALOG.write_text(catalog_text(recipes), encoding="utf-8")
|
||||
TRAINING.parent.mkdir(parents=True, exist_ok=True)
|
||||
with TRAINING.open("w", encoding="utf-8") as handle:
|
||||
for row in training_rows(recipes):
|
||||
handle.write(json.dumps(row, sort_keys=True) + "\n")
|
||||
print(f"wrote {CATALOG} ({len(recipes)} pairs)")
|
||||
print(f"wrote {TRAINING} ({len(recipes) * 2} examples)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract one function from public parent/fix blobs. Maintainer-only; CI stays offline.
|
||||
|
||||
Does not clone mozilla-central, gecko-dev, or Chromium. Advisory-only recipes
|
||||
(no parent and commit) are rejected. FixFox is not a fetch source.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
ALLOWED_HOSTS = {
|
||||
"raw.githubusercontent.com",
|
||||
"github.com",
|
||||
"phabricator.services.mozilla.com",
|
||||
"hg.mozilla.org",
|
||||
"hg-edge.mozilla.org",
|
||||
}
|
||||
BLOCKED_TOKENS = ("fixfox", "zenodo.org/record")
|
||||
|
||||
|
||||
class RecipeError(ValueError):
|
||||
"""Invalid harvest recipe."""
|
||||
|
||||
|
||||
def extract_function(source: str, function_name: str) -> str:
|
||||
"""Copy the named function through balanced braces."""
|
||||
needle = function_name
|
||||
idx = 0
|
||||
while True:
|
||||
found = source.find(needle, idx)
|
||||
if found < 0:
|
||||
raise RecipeError(f"function not found: {function_name}")
|
||||
before = source[found - 1] if found else " "
|
||||
after_name = source[found + len(needle) : found + len(needle) + 1]
|
||||
if before.isalnum() or before == "_":
|
||||
idx = found + len(needle)
|
||||
continue
|
||||
rest = source[found + len(needle) :].lstrip()
|
||||
if rest.startswith("(") or rest.startswith("<") or rest.startswith("{") or after_name in "(<{":
|
||||
brace = source.find("{", found)
|
||||
semi = source.find(";", found)
|
||||
if brace >= 0 and (semi < 0 or brace < semi):
|
||||
break
|
||||
idx = found + len(needle)
|
||||
line_start = source.rfind("\n", 0, found) + 1
|
||||
prev_nl = source.rfind("\n", 0, line_start - 1)
|
||||
prev_line = source[prev_nl + 1 : line_start]
|
||||
start = line_start
|
||||
stripped_prev = prev_line.strip()
|
||||
if (
|
||||
stripped_prev
|
||||
and not stripped_prev.startswith(("/", "*", "#", "//"))
|
||||
and "{" not in prev_line
|
||||
and ";" not in prev_line
|
||||
and not stripped_prev.startswith(("sub ", "def ", "class "))
|
||||
):
|
||||
start = prev_nl + 1
|
||||
brace = source.find("{", found)
|
||||
if brace < 0:
|
||||
raise RecipeError(f"no body for function: {function_name}")
|
||||
depth = 0
|
||||
for pos, char in enumerate(source[brace:], brace):
|
||||
if char == "{":
|
||||
depth += 1
|
||||
elif char == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[start : pos + 1].strip() + "\n"
|
||||
raise RecipeError(f"unbalanced braces for function: {function_name}")
|
||||
|
||||
|
||||
def provenance_header(recipe: dict[str, Any], *, side: str, sha: str) -> str:
|
||||
lines = [
|
||||
f"Provenance: {recipe.get('repo', '')} {recipe.get('function', '')} ({side}).",
|
||||
f"repo: {recipe.get('repo', '')}",
|
||||
f"commit: {sha}",
|
||||
f"parent: {recipe.get('parent', '')}",
|
||||
f"commit_url: {recipe.get('commit_url', '')}",
|
||||
f"cve: {recipe.get('cve', '')}",
|
||||
f"license: {recipe.get('license', '')}",
|
||||
f"function: {recipe.get('function', '')}",
|
||||
f"relpath: {recipe.get('relpath', recipe.get('path', ''))}",
|
||||
]
|
||||
prefix = "//" if str(recipe.get("language", "c")).startswith("c") and str(recipe.get("path", "")).endswith((".cc", ".cpp", ".java", ".js")) else "/*"
|
||||
if prefix == "//":
|
||||
return "\n".join(f"// {line}" for line in lines) + "\n"
|
||||
return "/* " + "\n * ".join(lines) + "\n */\n"
|
||||
|
||||
|
||||
def validate_recipe(recipe: dict[str, Any]) -> None:
|
||||
name = str(recipe.get("name", ""))
|
||||
if any(token in str(recipe).lower() for token in BLOCKED_TOKENS):
|
||||
raise RecipeError(f"{name}: embargoed or blocked dataset")
|
||||
if not recipe.get("parent") or not recipe.get("commit"):
|
||||
raise RecipeError(f"{name}: advisory-only recipe rejected; parent and commit required")
|
||||
if not recipe.get("path") or not recipe.get("function"):
|
||||
raise RecipeError(f"{name}: path and function required")
|
||||
if not recipe.get("license"):
|
||||
raise RecipeError(f"{name}: license required")
|
||||
|
||||
|
||||
def github_raw_url(repo: str, sha: str, path: str) -> str:
|
||||
return f"https://raw.githubusercontent.com/{repo}/{sha}/{path}"
|
||||
|
||||
|
||||
def excerpt_paths(recipe: dict[str, Any], root: Path) -> tuple[Path, Path]:
|
||||
name = str(recipe["name"])
|
||||
ext = Path(str(recipe.get("path", "a.c"))).suffix or ".c"
|
||||
folder = root / name
|
||||
return folder / f"vuln{ext}", folder / f"fixed{ext}"
|
||||
|
||||
|
||||
def write_excerpt(path: Path, recipe: dict[str, Any], *, side: str, sha: str, body: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
header = provenance_header(recipe, side=side, sha=sha)
|
||||
path.write_text(header + "\n" + body, encoding="utf-8")
|
||||
|
||||
|
||||
def harvest_recipe(recipe: dict[str, Any], root: Path) -> tuple[Path, Path]:
|
||||
validate_recipe(recipe)
|
||||
if str(recipe.get("host", "github")) != "github":
|
||||
raise RecipeError(f"{recipe.get('name')}: live fetch currently implemented for host=github only")
|
||||
parent_src = fetch_url(github_raw_url(str(recipe["repo"]), str(recipe["parent"]), str(recipe["path"])))
|
||||
fix_src = fetch_url(github_raw_url(str(recipe["repo"]), str(recipe["commit"]), str(recipe["path"])))
|
||||
function = str(recipe["function"])
|
||||
parent_fn = extract_function(parent_src, function)
|
||||
fix_fn = extract_function(fix_src, function)
|
||||
vuln_path, fixed_path = excerpt_paths(recipe, root)
|
||||
write_excerpt(vuln_path, recipe, side="vuln", sha=str(recipe["parent"]), body=parent_fn)
|
||||
write_excerpt(fixed_path, recipe, side="fixed", sha=str(recipe["commit"]), body=fix_fn)
|
||||
return vuln_path, fixed_path
|
||||
|
||||
|
||||
def fetch_url(url: str, timeout: int = 60) -> str:
|
||||
host = urlparse(url).hostname or ""
|
||||
if host not in ALLOWED_HOSTS:
|
||||
raise RecipeError(f"host not allowed: {host}")
|
||||
request = Request(url, headers={"User-Agent": "OpenUltraSAST-vfc-harvest"})
|
||||
with urlopen(request, timeout=timeout) as response: # noqa: S310 — host allowlisted
|
||||
return response.read().decode("utf-8", "replace")
|
||||
|
||||
|
||||
def load_recipes(path: Path) -> tuple[dict[str, Any], ...]:
|
||||
payload = tomllib.loads(path.read_text())
|
||||
return tuple(dict(item) for item in payload.get("recipe", []))
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--recipes", type=Path, default=Path(__file__).with_name("recipes.toml"))
|
||||
parser.add_argument("--name", help="recipe name to harvest (omit with --all)")
|
||||
parser.add_argument("--all", action="store_true", help="process every recipe")
|
||||
parser.add_argument("--fetch", action="store_true", help="HTTP-fetch and write excerpts; default is dry-run validate")
|
||||
parser.add_argument("--force", action="store_true", help="overwrite existing excerpts")
|
||||
parser.add_argument("--out", type=Path, default=Path(__file__).resolve().parent, help="excerpt root")
|
||||
args = parser.parse_args(argv)
|
||||
loaded = load_recipes(args.recipes)
|
||||
recipes = {str(item["name"]): item for item in loaded}
|
||||
selected: list[dict[str, Any]]
|
||||
if args.all:
|
||||
selected = list(loaded)
|
||||
elif args.name:
|
||||
if args.name not in recipes:
|
||||
print(f"unknown recipe: {args.name}", file=sys.stderr)
|
||||
return 2
|
||||
selected = [recipes[args.name]]
|
||||
else:
|
||||
print("pass --name RECIPE or --all", file=sys.stderr)
|
||||
return 2
|
||||
failures = 0
|
||||
for recipe in selected:
|
||||
name = str(recipe["name"])
|
||||
try:
|
||||
validate_recipe(recipe)
|
||||
except RecipeError as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
failures += 1
|
||||
continue
|
||||
if not args.fetch:
|
||||
print(f"ok {name} parent={recipe['parent']} commit={recipe['commit']}")
|
||||
continue
|
||||
vuln_path, fixed_path = excerpt_paths(recipe, args.out)
|
||||
if vuln_path.is_file() and fixed_path.is_file() and not args.force:
|
||||
print(f"skip {name} (exists)")
|
||||
continue
|
||||
try:
|
||||
harvest_recipe(recipe, args.out)
|
||||
except RecipeError as exc:
|
||||
print(f"{name}: {exc}", file=sys.stderr)
|
||||
failures += 1
|
||||
continue
|
||||
print(f"wrote {name} -> {vuln_path.parent}")
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Provenance: OpenSSL tls1_process_heartbeat after Heartbleed bounds check.
|
||||
* repo: openssl/openssl
|
||||
* commit: 96db9023b881d7cd9f379b0c154650d6c108e9a3
|
||||
* parent: 0d7717fc9c83dafab8153cbd5e2180e6e04cc802
|
||||
* commit_url: https://github.com/openssl/openssl/commit/96db9023b881d7cd9f379b0c154650d6c108e9a3
|
||||
* cve: CVE-2014-0160
|
||||
* license: OpenSSL License
|
||||
* function: tls1_process_heartbeat
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ssl_st {
|
||||
struct {
|
||||
struct {
|
||||
unsigned char *data;
|
||||
unsigned int length;
|
||||
} rrec;
|
||||
} *s3;
|
||||
int version;
|
||||
void (*msg_callback)(int write_p, int version, int content_type, const void *buf, size_t len, void *ssl, void *arg);
|
||||
void *msg_callback_arg;
|
||||
unsigned int tlsext_hb_seq;
|
||||
unsigned int tlsext_hb_pending;
|
||||
} SSL;
|
||||
|
||||
#define TLS1_RT_HEARTBEAT 24
|
||||
#define TLS1_HB_REQUEST 1
|
||||
#define TLS1_HB_RESPONSE 2
|
||||
#define n2s(c, s) ((s = (((unsigned int)((c)[0])) << 8) | (((unsigned int)((c)[1])))), (c) += 2)
|
||||
#define s2n(s, c) (((c)[0] = (unsigned char)(((s) >> 8) & 0xff), (c)[1] = (unsigned char)(((s)) & 0xff)), (c) += 2)
|
||||
|
||||
static unsigned char *OPENSSL_malloc(unsigned int n) { return (unsigned char *)malloc(n); }
|
||||
static void OPENSSL_free(void *p) { free(p); }
|
||||
static int RAND_pseudo_bytes(unsigned char *buf, unsigned int n) {
|
||||
(void)buf;
|
||||
(void)n;
|
||||
return 1;
|
||||
}
|
||||
static int ssl3_write_bytes(SSL *s, int type, unsigned char *buf, unsigned int len) {
|
||||
(void)s;
|
||||
(void)type;
|
||||
(void)buf;
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int tls1_process_heartbeat(SSL *s) {
|
||||
unsigned char *p = &s->s3->rrec.data[0], *pl;
|
||||
unsigned short hbtype;
|
||||
unsigned int payload;
|
||||
unsigned int padding = 16; /* Use minimum padding */
|
||||
|
||||
if (s->msg_callback)
|
||||
s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg);
|
||||
|
||||
/* Read type and payload length first */
|
||||
if (1 + 2 + 16 > s->s3->rrec.length)
|
||||
return 0; /* silently discard */
|
||||
hbtype = *p++;
|
||||
n2s(p, payload);
|
||||
if (1 + 2 + payload + 16 > s->s3->rrec.length)
|
||||
return 0; /* silently discard per RFC 6520 sec. 4 */
|
||||
pl = p;
|
||||
|
||||
if (hbtype == TLS1_HB_REQUEST) {
|
||||
unsigned char *buffer, *bp;
|
||||
int r;
|
||||
|
||||
/* Allocate memory for the response, size is 1 bytes
|
||||
* message type, plus 2 bytes payload length, plus
|
||||
* payload, plus padding
|
||||
*/
|
||||
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
|
||||
bp = buffer;
|
||||
|
||||
/* Enter response type, length and copy payload */
|
||||
*bp++ = TLS1_HB_RESPONSE;
|
||||
s2n(payload, bp);
|
||||
memcpy(bp, pl, payload);
|
||||
bp += payload;
|
||||
/* Random padding */
|
||||
RAND_pseudo_bytes(bp, padding);
|
||||
|
||||
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
|
||||
|
||||
if (r >= 0 && s->msg_callback)
|
||||
s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg);
|
||||
|
||||
OPENSSL_free(buffer);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (hbtype == TLS1_HB_RESPONSE) {
|
||||
unsigned int seq;
|
||||
|
||||
/* We only send sequence numbers (2 bytes unsigned int),
|
||||
* and 16 random bytes, so we just try to read the
|
||||
* sequence number */
|
||||
n2s(pl, seq);
|
||||
|
||||
if (payload == 18 && seq == s->tlsext_hb_seq) {
|
||||
s->tlsext_hb_seq++;
|
||||
s->tlsext_hb_pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Provenance: OpenSSL tls1_process_heartbeat before Heartbleed fix.
|
||||
* repo: openssl/openssl
|
||||
* commit: 0d7717fc9c83dafab8153cbd5e2180e6e04cc802
|
||||
* parent: 0d7717fc9c83dafab8153cbd5e2180e6e04cc802
|
||||
* fix: 96db9023b881d7cd9f379b0c154650d6c108e9a3
|
||||
* commit_url: https://github.com/openssl/openssl/commit/96db9023b881d7cd9f379b0c154650d6c108e9a3
|
||||
* cve: CVE-2014-0160
|
||||
* license: OpenSSL License
|
||||
* function: tls1_process_heartbeat
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ssl_st {
|
||||
struct {
|
||||
struct {
|
||||
unsigned char *data;
|
||||
unsigned int length;
|
||||
} rrec;
|
||||
} *s3;
|
||||
int version;
|
||||
void (*msg_callback)(int write_p, int version, int content_type, const void *buf, size_t len, void *ssl, void *arg);
|
||||
void *msg_callback_arg;
|
||||
unsigned int tlsext_hb_seq;
|
||||
unsigned int tlsext_hb_pending;
|
||||
} SSL;
|
||||
|
||||
#define TLS1_RT_HEARTBEAT 24
|
||||
#define TLS1_HB_REQUEST 1
|
||||
#define TLS1_HB_RESPONSE 2
|
||||
#define n2s(c, s) ((s = (((unsigned int)((c)[0])) << 8) | (((unsigned int)((c)[1])))), (c) += 2)
|
||||
#define s2n(s, c) (((c)[0] = (unsigned char)(((s) >> 8) & 0xff), (c)[1] = (unsigned char)(((s)) & 0xff)), (c) += 2)
|
||||
|
||||
static unsigned char *OPENSSL_malloc(unsigned int n) { return (unsigned char *)malloc(n); }
|
||||
static void OPENSSL_free(void *p) { free(p); }
|
||||
static int RAND_pseudo_bytes(unsigned char *buf, unsigned int n) {
|
||||
(void)buf;
|
||||
(void)n;
|
||||
return 1;
|
||||
}
|
||||
static int ssl3_write_bytes(SSL *s, int type, unsigned char *buf, unsigned int len) {
|
||||
(void)s;
|
||||
(void)type;
|
||||
(void)buf;
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int tls1_process_heartbeat(SSL *s) {
|
||||
unsigned char *p = &s->s3->rrec.data[0], *pl;
|
||||
unsigned short hbtype;
|
||||
unsigned int payload;
|
||||
unsigned int padding = 16; /* Use minimum padding */
|
||||
|
||||
/* Read type and payload length first */
|
||||
hbtype = *p++;
|
||||
n2s(p, payload);
|
||||
pl = p;
|
||||
|
||||
if (s->msg_callback)
|
||||
s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg);
|
||||
|
||||
if (hbtype == TLS1_HB_REQUEST) {
|
||||
unsigned char *buffer, *bp;
|
||||
int r;
|
||||
|
||||
/* Allocate memory for the response, size is 1 bytes
|
||||
* message type, plus 2 bytes payload length, plus
|
||||
* payload, plus padding
|
||||
*/
|
||||
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
|
||||
bp = buffer;
|
||||
|
||||
/* Enter response type, length and copy payload */
|
||||
*bp++ = TLS1_HB_RESPONSE;
|
||||
s2n(payload, bp);
|
||||
memcpy(bp, pl, payload);
|
||||
bp += payload;
|
||||
/* Random padding */
|
||||
RAND_pseudo_bytes(bp, padding);
|
||||
|
||||
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
|
||||
|
||||
if (r >= 0 && s->msg_callback)
|
||||
s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg);
|
||||
|
||||
OPENSSL_free(buffer);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (hbtype == TLS1_HB_RESPONSE) {
|
||||
unsigned int seq;
|
||||
|
||||
/* We only send sequence numbers (2 bytes unsigned int),
|
||||
* and 16 random bytes, so we just try to read the
|
||||
* sequence number */
|
||||
n2s(pl, seq);
|
||||
|
||||
if (payload == 18 && seq == s->tlsext_hb_seq) {
|
||||
s->tlsext_hb_seq++;
|
||||
s->tlsext_hb_pending = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Provenance: openssl/openssl tls1_check_sig_alg (fixed).
|
||||
* repo: openssl/openssl
|
||||
* commit: a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* parent: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* commit_url: https://github.com/openssl/openssl/commit/a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* cve: CVE-2020-1967
|
||||
* license: OpenSSL License
|
||||
* function: tls1_check_sig_alg
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
|
||||
static int tls1_check_sig_alg(SSL *s, X509 *x, int default_nid)
|
||||
{
|
||||
int sig_nid, use_pc_sigalgs = 0;
|
||||
size_t i;
|
||||
const SIGALG_LOOKUP *sigalg;
|
||||
size_t sigalgslen;
|
||||
if (default_nid == -1)
|
||||
return 1;
|
||||
sig_nid = X509_get_signature_nid(x);
|
||||
if (default_nid)
|
||||
return sig_nid == default_nid ? 1 : 0;
|
||||
|
||||
if (SSL_IS_TLS13(s) && s->s3.tmp.peer_cert_sigalgs != NULL) {
|
||||
/*
|
||||
* If we're in TLSv1.3 then we only get here if we're checking the
|
||||
* chain. If the peer has specified peer_cert_sigalgs then we use them
|
||||
* otherwise we default to normal sigalgs.
|
||||
*/
|
||||
sigalgslen = s->s3.tmp.peer_cert_sigalgslen;
|
||||
use_pc_sigalgs = 1;
|
||||
} else {
|
||||
sigalgslen = s->shared_sigalgslen;
|
||||
}
|
||||
for (i = 0; i < sigalgslen; i++) {
|
||||
sigalg = use_pc_sigalgs
|
||||
? tls1_lookup_sigalg(s->s3.tmp.peer_cert_sigalgs[i])
|
||||
: s->shared_sigalgs[i];
|
||||
if (sigalg != NULL && sig_nid == sigalg->sigandhash)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Provenance: openssl/openssl tls1_check_sig_alg (vuln).
|
||||
* repo: openssl/openssl
|
||||
* commit: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* parent: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* commit_url: https://github.com/openssl/openssl/commit/a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* cve: CVE-2020-1967
|
||||
* license: OpenSSL License
|
||||
* function: tls1_check_sig_alg
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
|
||||
static int tls1_check_sig_alg(SSL *s, X509 *x, int default_nid)
|
||||
{
|
||||
int sig_nid, use_pc_sigalgs = 0;
|
||||
size_t i;
|
||||
const SIGALG_LOOKUP *sigalg;
|
||||
size_t sigalgslen;
|
||||
if (default_nid == -1)
|
||||
return 1;
|
||||
sig_nid = X509_get_signature_nid(x);
|
||||
if (default_nid)
|
||||
return sig_nid == default_nid ? 1 : 0;
|
||||
|
||||
if (SSL_IS_TLS13(s) && s->s3.tmp.peer_cert_sigalgs != NULL) {
|
||||
/*
|
||||
* If we're in TLSv1.3 then we only get here if we're checking the
|
||||
* chain. If the peer has specified peer_cert_sigalgs then we use them
|
||||
* otherwise we default to normal sigalgs.
|
||||
*/
|
||||
sigalgslen = s->s3.tmp.peer_cert_sigalgslen;
|
||||
use_pc_sigalgs = 1;
|
||||
} else {
|
||||
sigalgslen = s->shared_sigalgslen;
|
||||
}
|
||||
for (i = 0; i < sigalgslen; i++) {
|
||||
sigalg = use_pc_sigalgs
|
||||
? tls1_lookup_sigalg(s->s3.tmp.peer_cert_sigalgs[i])
|
||||
: s->shared_sigalgs[i];
|
||||
if (sig_nid == sigalg->sigandhash)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Provenance: openssl/openssl tls1_check_sig_alg (fixed).
|
||||
* repo: openssl/openssl
|
||||
* commit: a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* parent: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* commit_url: https://github.com/openssl/openssl/commit/a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* cve: CVE-2020-1967
|
||||
* license: OpenSSL License
|
||||
* function: tls1_check_sig_alg
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
|
||||
static int tls1_check_sig_alg(SSL *s, X509 *x, int default_nid)
|
||||
{
|
||||
int sig_nid, use_pc_sigalgs = 0;
|
||||
size_t i;
|
||||
const SIGALG_LOOKUP *sigalg;
|
||||
size_t sigalgslen;
|
||||
if (default_nid == -1)
|
||||
return 1;
|
||||
sig_nid = X509_get_signature_nid(x);
|
||||
if (default_nid)
|
||||
return sig_nid == default_nid ? 1 : 0;
|
||||
|
||||
if (SSL_IS_TLS13(s) && s->s3.tmp.peer_cert_sigalgs != NULL) {
|
||||
/*
|
||||
* If we're in TLSv1.3 then we only get here if we're checking the
|
||||
* chain. If the peer has specified peer_cert_sigalgs then we use them
|
||||
* otherwise we default to normal sigalgs.
|
||||
*/
|
||||
sigalgslen = s->s3.tmp.peer_cert_sigalgslen;
|
||||
use_pc_sigalgs = 1;
|
||||
} else {
|
||||
sigalgslen = s->shared_sigalgslen;
|
||||
}
|
||||
for (i = 0; i < sigalgslen; i++) {
|
||||
sigalg = use_pc_sigalgs
|
||||
? tls1_lookup_sigalg(s->s3.tmp.peer_cert_sigalgs[i])
|
||||
: s->shared_sigalgs[i];
|
||||
if (sigalg != NULL && sig_nid == sigalg->sigandhash)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Provenance: openssl/openssl tls1_check_sig_alg (vuln).
|
||||
* repo: openssl/openssl
|
||||
* commit: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* parent: 3656c08ab4b1b892730cb5e808b6f4298b08a2e6
|
||||
* commit_url: https://github.com/openssl/openssl/commit/a87f3fe01a5a894aa27ccd6a239155fd129988e4
|
||||
* cve: CVE-2020-1967
|
||||
* license: OpenSSL License
|
||||
* function: tls1_check_sig_alg
|
||||
* relpath: ssl/t1_lib.c
|
||||
*/
|
||||
|
||||
static int tls1_check_sig_alg(SSL *s, X509 *x, int default_nid)
|
||||
{
|
||||
int sig_nid, use_pc_sigalgs = 0;
|
||||
size_t i;
|
||||
const SIGALG_LOOKUP *sigalg;
|
||||
size_t sigalgslen;
|
||||
if (default_nid == -1)
|
||||
return 1;
|
||||
sig_nid = X509_get_signature_nid(x);
|
||||
if (default_nid)
|
||||
return sig_nid == default_nid ? 1 : 0;
|
||||
|
||||
if (SSL_IS_TLS13(s) && s->s3.tmp.peer_cert_sigalgs != NULL) {
|
||||
/*
|
||||
* If we're in TLSv1.3 then we only get here if we're checking the
|
||||
* chain. If the peer has specified peer_cert_sigalgs then we use them
|
||||
* otherwise we default to normal sigalgs.
|
||||
*/
|
||||
sigalgslen = s->s3.tmp.peer_cert_sigalgslen;
|
||||
use_pc_sigalgs = 1;
|
||||
} else {
|
||||
sigalgslen = s->shared_sigalgslen;
|
||||
}
|
||||
for (i = 0; i < sigalgslen; i++) {
|
||||
sigalg = use_pc_sigalgs
|
||||
? tls1_lookup_sigalg(s->s3.tmp.peer_cert_sigalgs[i])
|
||||
: s->shared_sigalgs[i];
|
||||
if (sig_nid == sigalg->sigandhash)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/* Provenance: openssl/openssl GENERAL_NAME_cmp (fixed).
|
||||
* repo: openssl/openssl
|
||||
* commit: f960d81215ebf3f65e03d4d5d857fb9b666d6920
|
||||
* parent: aa0ad2011d3e7ad8a611da274ef7d9c7706e289b
|
||||
* commit_url: https://github.com/openssl/openssl/commit/f960d81215ebf3f65e03d4d5d857fb9b666d6920
|
||||
* cve: CVE-2020-1971
|
||||
* license: OpenSSL License
|
||||
* function: GENERAL_NAME_cmp
|
||||
* relpath: crypto/x509v3/v3_genn.c
|
||||
*/
|
||||
|
||||
int GENERAL_NAME_cmp(GENERAL_NAME *a, GENERAL_NAME *b)
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
if (!a || !b || a->type != b->type)
|
||||
return -1;
|
||||
switch (a->type) {
|
||||
case GEN_X400:
|
||||
result = ASN1_TYPE_cmp(a->d.x400Address, b->d.x400Address);
|
||||
break;
|
||||
|
||||
case GEN_EDIPARTY:
|
||||
result = edipartyname_cmp(a->d.ediPartyName, b->d.ediPartyName);
|
||||
break;
|
||||
|
||||
case GEN_OTHERNAME:
|
||||
result = OTHERNAME_cmp(a->d.otherName, b->d.otherName);
|
||||
break;
|
||||
|
||||
case GEN_EMAIL:
|
||||
case GEN_DNS:
|
||||
case GEN_URI:
|
||||
result = ASN1_STRING_cmp(a->d.ia5, b->d.ia5);
|
||||
break;
|
||||
|
||||
case GEN_DIRNAME:
|
||||
result = X509_NAME_cmp(a->d.dirn, b->d.dirn);
|
||||
break;
|
||||
|
||||
case GEN_IPADD:
|
||||
result = ASN1_OCTET_STRING_cmp(a->d.ip, b->d.ip);
|
||||
break;
|
||||
|
||||
case GEN_RID:
|
||||
result = OBJ_cmp(a->d.rid, b->d.rid);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user