Skip to content

Commit dce6f43

Browse files
tniessenRafaelGSS
authored andcommitted
crypto: revert dangerous uses of std::string_view
An `std::string_view v` is a `const char* v.data()` along with an `std::size_t v.size()` that guarantees that `v.size()` contiguous elements of type `char` can be accessed relative to the pointer `v.data()`. One of the main reasons behind the existence of `std::string_view` is the ability to operate on `char` sequences without requiring null termination, which otherwise often requires expensive copies of strings to be made. As a consequence, it is generally incorrect to assume that `v.data()` points to a null-terminated sequence of `char`, and the only way to obtain a null-terminated string from an `std::string_view` is to make a copy. It is not even possible to check if the sequence pointed to by `v.data()` is null-terminated because the null character would be at position `v.data() + v.size()`, which is outside of the range that `v` guarantees safe access to. (A default-constructed `std::string_view` even sets its own data pointer to a `nullptr`, which is fine because it only needs to guarantee safe access to zero elements, i.e., to no elements). In `deps/ncrypto` and `src/crypto`, there are various APIs that consume `std::string_view v` arguments but then ignore `v.size()` and treat `v.data()` as a C-style string of type `const char*`. However, that is not what call sites would expect from functions that explicitly ask for `std::string_view` arguments, since it makes assumptions beyond the guarantees provided by `std::string_view` and leads to undefined behavior unless the given view either contains an embedded null character or the `char` at address `v.data() + v.size()` is a null character. This is not a reasonable assumption for `std::string_view` in general, and it also defeats the purpose of `std::string_view` for the most part since, when `v.size()` is being ignored, it is essentially just a `const char*`. Constructing an `std::string_view` from a `const char*` is also not "free" but requires computing the length of the C-style string (unless the length can be computed at compile time, e.g., because the value is just a string literal). Repeated conversion between `const char*` as used by OpenSSL and `std::string_view` as used by ncrypto thus incurs the additional overhead of computing the length of the string whenever an `std::string_view` is constructed from a `const char*`. (This seems negligible compared to the safety argument though.) Similarly, returning a `const char*` pointer to a C-style string as an `std::string_view` has two downsides: the function must compute the length of the string in order to construct the view, and the caller can no longer assume that the return value is null-terminated and thus cannot pass the returned view to functions that require their arguments to be null terminated. (And, for the reasons explained above, the caller also cannot check if the value is null-terminated without potentially invoking undefined behavior.) C++20 unfortunately does not have a type similar to Rust's `CStr` or GSL `czstring`. Therefore, this commit changes many occurrences of `std::string_view` back to `const char*`, which is conventional for null-terminated C-style strings and does not require computing the length of strings. There are _a lot_ of occurrences of `std::string_view` in ncrypto and for each one, we need to evaluate if it is safe and a good abstraction. I tried to do so, but I might have changed too few or too many, so please feel free to give feedback on individual occurrences. PR-URL: #57816 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Darshan Sen <[email protected]>
1 parent baa6968 commit dce6f43

17 files changed

+83
-89
lines changed

deps/ncrypto/engine.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ ENGINE* EnginePointer::release() {
4444
return ret;
4545
}
4646

47-
EnginePointer EnginePointer::getEngineByName(const std::string_view name,
47+
EnginePointer EnginePointer::getEngineByName(const char* name,
4848
CryptoErrorList* errors) {
4949
MarkPopErrorOnReturn mark_pop_error_on_return(errors);
50-
EnginePointer engine(ENGINE_by_id(name.data()));
50+
EnginePointer engine(ENGINE_by_id(name));
5151
if (!engine) {
5252
// Engine not found, try loading dynamically.
5353
engine = EnginePointer(ENGINE_by_id("dynamic"));
5454
if (engine) {
55-
if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name.data(), 0) ||
55+
if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name, 0) ||
5656
!ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) {
5757
engine.reset();
5858
}
@@ -73,10 +73,10 @@ bool EnginePointer::init(bool finish_on_exit) {
7373
return ENGINE_init(engine) == 1;
7474
}
7575

76-
EVPKeyPointer EnginePointer::loadPrivateKey(const std::string_view key_name) {
76+
EVPKeyPointer EnginePointer::loadPrivateKey(const char* key_name) {
7777
if (engine == nullptr) return EVPKeyPointer();
7878
return EVPKeyPointer(
79-
ENGINE_load_private_key(engine, key_name.data(), nullptr, nullptr));
79+
ENGINE_load_private_key(engine, key_name, nullptr, nullptr));
8080
}
8181

8282
void EnginePointer::initEnginesOnce() {

deps/ncrypto/ncrypto.cc

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) {
13251325
// When adding or removing errors below, please also update the list in the API
13261326
// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md
13271327
// Also *please* update the respective section in doc/api/tls.md as well
1328-
std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
1328+
const char* X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
13291329
#define CASE(CODE) \
13301330
case X509_V_ERR_##CODE: \
13311331
return #CODE;
@@ -1363,7 +1363,7 @@ std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
13631363
return "UNSPECIFIED";
13641364
}
13651365

1366-
std::optional<std::string_view> X509Pointer::ErrorReason(int32_t err) {
1366+
std::optional<const char*> X509Pointer::ErrorReason(int32_t err) {
13671367
if (err == X509_V_OK) return std::nullopt;
13681368
return X509_verify_cert_error_string(err);
13691369
}
@@ -1419,9 +1419,8 @@ BIOPointer BIOPointer::New(const void* data, size_t len) {
14191419
return BIOPointer(BIO_new_mem_buf(data, len));
14201420
}
14211421

1422-
BIOPointer BIOPointer::NewFile(std::string_view filename,
1423-
std::string_view mode) {
1424-
return BIOPointer(BIO_new_file(filename.data(), mode.data()));
1422+
BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) {
1423+
return BIOPointer(BIO_new_file(filename, mode));
14251424
}
14261425

14271426
BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) {
@@ -1703,17 +1702,17 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
17031702
// ============================================================================
17041703
// KDF
17051704

1706-
const EVP_MD* getDigestByName(const std::string_view name) {
1705+
const EVP_MD* getDigestByName(const char* name) {
17071706
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1
17081707
// exposed through the public API.
1709-
if (name == "dss1" || name == "DSS1") [[unlikely]] {
1708+
if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] {
17101709
return EVP_sha1();
17111710
}
1712-
return EVP_get_digestbyname(name.data());
1711+
return EVP_get_digestbyname(name);
17131712
}
17141713

1715-
const EVP_CIPHER* getCipherByName(const std::string_view name) {
1716-
return EVP_get_cipherbyname(name.data());
1714+
const EVP_CIPHER* getCipherByName(const char* name) {
1715+
return EVP_get_cipherbyname(name);
17171716
}
17181717

17191718
bool checkHkdfLength(const Digest& md, size_t length) {
@@ -2560,8 +2559,7 @@ SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) {
25602559
return SSLPointer(SSL_new(ctx.get()));
25612560
}
25622561

2563-
void SSLPointer::getCiphers(
2564-
std::function<void(const std::string_view)> cb) const {
2562+
void SSLPointer::getCiphers(std::function<void(const char*)> cb) const {
25652563
if (!ssl_) return;
25662564
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get());
25672565

@@ -2626,7 +2624,7 @@ std::optional<uint32_t> SSLPointer::verifyPeerCertificate() const {
26262624
return std::nullopt;
26272625
}
26282626

2629-
const std::string_view SSLPointer::getClientHelloAlpn() const {
2627+
const char* SSLPointer::getClientHelloAlpn() const {
26302628
if (ssl_ == nullptr) return {};
26312629
#ifndef OPENSSL_IS_BORINGSSL
26322630
const unsigned char* buf;
@@ -2651,7 +2649,7 @@ const std::string_view SSLPointer::getClientHelloAlpn() const {
26512649
#endif
26522650
}
26532651

2654-
const std::string_view SSLPointer::getClientHelloServerName() const {
2652+
const char* SSLPointer::getClientHelloServerName() const {
26552653
if (ssl_ == nullptr) return {};
26562654
#ifndef OPENSSL_IS_BORINGSSL
26572655
const unsigned char* buf;
@@ -2794,10 +2792,10 @@ bool SSLCtxPointer::setGroups(const char* groups) {
27942792
return SSL_CTX_set1_groups_list(get(), groups) == 1;
27952793
}
27962794

2797-
bool SSLCtxPointer::setCipherSuites(std::string_view ciphers) {
2795+
bool SSLCtxPointer::setCipherSuites(const char* ciphers) {
27982796
#ifndef OPENSSL_IS_BORINGSSL
27992797
if (!ctx_) return false;
2800-
return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers.data());
2798+
return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers);
28012799
#else
28022800
// BoringSSL does not allow API config of TLS 1.3 cipher suites.
28032801
// We treat this as a non-op.
@@ -2807,8 +2805,8 @@ bool SSLCtxPointer::setCipherSuites(std::string_view ciphers) {
28072805

28082806
// ============================================================================
28092807

2810-
const Cipher Cipher::FromName(std::string_view name) {
2811-
return Cipher(EVP_get_cipherbyname(name.data()));
2808+
const Cipher Cipher::FromName(const char* name) {
2809+
return Cipher(EVP_get_cipherbyname(name));
28122810
}
28132811

28142812
const Cipher Cipher::FromNid(int nid) {
@@ -2922,7 +2920,7 @@ std::string_view Cipher::getModeLabel() const {
29222920
return "{unknown}";
29232921
}
29242922

2925-
std::string_view Cipher::getName() const {
2923+
const char* Cipher::getName() const {
29262924
if (!cipher_) return {};
29272925
// OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of
29282926
// EVP_CIPHER_name(cipher) for compatibility with BoringSSL.
@@ -3839,7 +3837,7 @@ DataPointer Cipher::recover(const EVPKeyPointer& key,
38393837
namespace {
38403838
struct CipherCallbackContext {
38413839
Cipher::CipherNameCallback cb;
3842-
void operator()(std::string_view name) { cb(name); }
3840+
void operator()(const char* name) { cb(name); }
38433841
};
38443842

38453843
#if OPENSSL_VERSION_MAJOR >= 3
@@ -3918,10 +3916,10 @@ int Ec::getCurve() const {
39183916
return EC_GROUP_get_curve_name(getGroup());
39193917
}
39203918

3921-
int Ec::GetCurveIdFromName(std::string_view name) {
3922-
int nid = EC_curve_nist2nid(name.data());
3919+
int Ec::GetCurveIdFromName(const char* name) {
3920+
int nid = EC_curve_nist2nid(name);
39233921
if (nid == NID_undef) {
3924-
nid = OBJ_sn2nid(name.data());
3922+
nid = OBJ_sn2nid(name);
39253923
}
39263924
return nid;
39273925
}
@@ -4294,7 +4292,7 @@ const Digest Digest::SHA256 = Digest(EVP_sha256());
42944292
const Digest Digest::SHA384 = Digest(EVP_sha384());
42954293
const Digest Digest::SHA512 = Digest(EVP_sha512());
42964294

4297-
const Digest Digest::FromName(std::string_view name) {
4295+
const Digest Digest::FromName(const char* name) {
42984296
return ncrypto::getDigestByName(name);
42994297
}
43004298

deps/ncrypto/ncrypto.h

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ class Digest final {
272272
static const Digest SHA384;
273273
static const Digest SHA512;
274274

275-
static const Digest FromName(std::string_view name);
275+
static const Digest FromName(const char* name);
276276

277277
private:
278278
const EVP_MD* md_ = nullptr;
@@ -314,7 +314,7 @@ class Cipher final {
314314
int getKeyLength() const;
315315
int getBlockSize() const;
316316
std::string_view getModeLabel() const;
317-
std::string_view getName() const;
317+
const char* getName() const;
318318

319319
bool isGcmMode() const;
320320
bool isWrapMode() const;
@@ -331,11 +331,11 @@ class Cipher final {
331331
unsigned char* key,
332332
unsigned char* iv) const;
333333

334-
static const Cipher FromName(std::string_view name);
334+
static const Cipher FromName(const char* name);
335335
static const Cipher FromNid(int nid);
336336
static const Cipher FromCtx(const CipherCtxPointer& ctx);
337337

338-
using CipherNameCallback = std::function<void(std::string_view name)>;
338+
using CipherNameCallback = std::function<void(const char* name)>;
339339

340340
// Iterates the known ciphers if the underlying implementation
341341
// is able to do so.
@@ -477,9 +477,9 @@ class Ec final {
477477
inline operator bool() const { return ec_ != nullptr; }
478478
inline operator OSSL3_CONST EC_KEY*() const { return ec_; }
479479

480-
static int GetCurveIdFromName(std::string_view name);
480+
static int GetCurveIdFromName(const char* name);
481481

482-
using GetCurveCallback = std::function<bool(std::string_view)>;
482+
using GetCurveCallback = std::function<bool(const char*)>;
483483
static bool GetCurves(GetCurveCallback callback);
484484

485485
private:
@@ -568,7 +568,7 @@ class BIOPointer final {
568568
static BIOPointer New(const BIO_METHOD* method);
569569
static BIOPointer New(const void* data, size_t len);
570570
static BIOPointer New(const BIGNUM* bn);
571-
static BIOPointer NewFile(std::string_view filename, std::string_view mode);
571+
static BIOPointer NewFile(const char* filename, const char* mode);
572572
static BIOPointer NewFp(FILE* fd, int flags);
573573

574574
template <typename T>
@@ -941,9 +941,8 @@ class DHPointer final {
941941
static BignumPointer GetStandardGenerator();
942942

943943
static BignumPointer FindGroup(
944-
const std::string_view name,
945-
FindGroupOption option = FindGroupOption::NONE);
946-
static DHPointer FromGroup(const std::string_view name,
944+
std::string_view name, FindGroupOption option = FindGroupOption::NONE);
945+
static DHPointer FromGroup(std::string_view name,
947946
FindGroupOption option = FindGroupOption::NONE);
948947

949948
static DHPointer New(BignumPointer&& p, BignumPointer&& g);
@@ -1042,7 +1041,7 @@ class SSLCtxPointer final {
10421041
SSL_CTX_set_tlsext_status_arg(get(), nullptr);
10431042
}
10441043

1045-
bool setCipherSuites(std::string_view ciphers);
1044+
bool setCipherSuites(const char* ciphers);
10461045

10471046
static SSLCtxPointer NewServer();
10481047
static SSLCtxPointer NewClient();
@@ -1071,8 +1070,8 @@ class SSLPointer final {
10711070
bool setSession(const SSLSessionPointer& session);
10721071
bool setSniContext(const SSLCtxPointer& ctx) const;
10731072

1074-
const std::string_view getClientHelloAlpn() const;
1075-
const std::string_view getClientHelloServerName() const;
1073+
const char* getClientHelloAlpn() const;
1074+
const char* getClientHelloServerName() const;
10761075

10771076
std::optional<const std::string_view> getServerName() const;
10781077
X509View getCertificate() const;
@@ -1088,7 +1087,7 @@ class SSLPointer final {
10881087

10891088
static std::optional<int> getSecurityLevel();
10901089

1091-
void getCiphers(std::function<void(const std::string_view)> cb) const;
1090+
void getCiphers(std::function<void(const char*)> cb) const;
10921091

10931092
static SSLPointer New(const SSLCtxPointer& ctx);
10941093
static std::optional<const std::string_view> GetServerName(const SSL* ssl);
@@ -1184,13 +1183,13 @@ class X509View final {
11841183
INVALID_NAME,
11851184
OPERATION_FAILED,
11861185
};
1187-
CheckMatch checkHost(const std::string_view host,
1186+
CheckMatch checkHost(std::string_view host,
11881187
int flags,
11891188
DataPointer* peerName = nullptr) const;
1190-
CheckMatch checkEmail(const std::string_view email, int flags) const;
1191-
CheckMatch checkIp(const std::string_view ip, int flags) const;
1189+
CheckMatch checkEmail(std::string_view email, int flags) const;
1190+
CheckMatch checkIp(std::string_view ip, int flags) const;
11921191

1193-
using UsageCallback = std::function<void(std::string_view)>;
1192+
using UsageCallback = std::function<void(const char*)>;
11941193
bool enumUsages(UsageCallback callback) const;
11951194

11961195
template <typename T>
@@ -1227,8 +1226,8 @@ class X509Pointer final {
12271226
X509View view() const;
12281227
operator X509View() const { return view(); }
12291228

1230-
static std::string_view ErrorCode(int32_t err);
1231-
static std::optional<std::string_view> ErrorReason(int32_t err);
1229+
static const char* ErrorCode(int32_t err);
1230+
static std::optional<const char*> ErrorReason(int32_t err);
12321231

12331232
private:
12341233
DeleteFnPtr<X509, X509_free> cert_;
@@ -1444,15 +1443,15 @@ class EnginePointer final {
14441443

14451444
bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr);
14461445
bool init(bool finish_on_exit = false);
1447-
EVPKeyPointer loadPrivateKey(const std::string_view key_name);
1446+
EVPKeyPointer loadPrivateKey(const char* key_name);
14481447

14491448
// Release ownership of the ENGINE* pointer.
14501449
ENGINE* release();
14511450

14521451
// Retrieve an OpenSSL Engine instance by name. If the name does not
14531452
// identify a valid named engine, the returned EnginePointer will be
14541453
// empty.
1455-
static EnginePointer getEngineByName(const std::string_view name,
1454+
static EnginePointer getEngineByName(const char* name,
14561455
CryptoErrorList* errors = nullptr);
14571456

14581457
// Call once when initializing OpenSSL at startup for the process.
@@ -1501,8 +1500,8 @@ Buffer<char> ExportChallenge(const char* input, size_t length);
15011500
// ============================================================================
15021501
// KDF
15031502

1504-
const EVP_MD* getDigestByName(const std::string_view name);
1505-
const EVP_CIPHER* getCipherByName(const std::string_view name);
1503+
const EVP_MD* getDigestByName(const char* name);
1504+
const EVP_CIPHER* getCipherByName(const char* name);
15061505

15071506
// Verify that the specified HKDF output length is valid for the given digest.
15081507
// The maximum length for HKDF output for a given digest is 255 times the

src/crypto/crypto_cipher.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
4848
const auto cipher = ([&] {
4949
if (args[1]->IsString()) {
5050
Utf8Value name(env->isolate(), args[1]);
51-
return Cipher::FromName(name.ToStringView());
51+
return Cipher::FromName(*name);
5252
} else {
5353
int nid = args[1].As<Int32>()->Value();
5454
return Cipher::FromNid(nid);
@@ -117,7 +117,7 @@ void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
117117

118118
if (info->Set(env->context(),
119119
env->name_string(),
120-
OneByteString(env->isolate(), name.data(), name.length()))
120+
OneByteString(env->isolate(), name))
121121
.IsNothing()) {
122122
return;
123123
}
@@ -303,7 +303,7 @@ void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
303303
new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher);
304304
}
305305

306-
void CipherBase::CommonInit(std::string_view cipher_type,
306+
void CipherBase::CommonInit(const char* cipher_type,
307307
const ncrypto::Cipher& cipher,
308308
const unsigned char* key,
309309
int key_len,
@@ -345,7 +345,7 @@ void CipherBase::CommonInit(std::string_view cipher_type,
345345
}
346346
}
347347

348-
void CipherBase::InitIv(std::string_view cipher_type,
348+
void CipherBase::InitIv(const char* cipher_type,
349349
const ByteSource& key_buf,
350350
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
351351
unsigned int auth_tag_len) {
@@ -425,10 +425,10 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
425425
auth_tag_len = kNoAuthTagLength;
426426
}
427427

428-
cipher->InitIv(cipher_type.ToStringView(), key_buf, iv_buf, auth_tag_len);
428+
cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len);
429429
}
430430

431-
bool CipherBase::InitAuthenticated(std::string_view cipher_type,
431+
bool CipherBase::InitAuthenticated(const char* cipher_type,
432432
int iv_len,
433433
unsigned int auth_tag_len) {
434434
CHECK(IsAuthenticatedMode());
@@ -933,7 +933,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
933933
Digest digest;
934934
if (args[offset + 2]->IsString()) {
935935
Utf8Value oaep_str(env->isolate(), args[offset + 2]);
936-
digest = Digest::FromName(oaep_str.ToStringView());
936+
digest = Digest::FromName(*oaep_str);
937937
if (!digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
938938
}
939939

0 commit comments

Comments
 (0)