Skip to content

Commit bea5e74

Browse files
committed
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.
1 parent 9bbbe60 commit bea5e74

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;
@@ -306,7 +306,7 @@ class Cipher final {
306306
int getKeyLength() const;
307307
int getBlockSize() const;
308308
std::string_view getModeLabel() const;
309-
std::string_view getName() const;
309+
const char* getName() const;
310310

311311
bool isGcmMode() const;
312312
bool isWrapMode() const;
@@ -323,11 +323,11 @@ class Cipher final {
323323
unsigned char* key,
324324
unsigned char* iv) const;
325325

326-
static const Cipher FromName(std::string_view name);
326+
static const Cipher FromName(const char* name);
327327
static const Cipher FromNid(int nid);
328328
static const Cipher FromCtx(const CipherCtxPointer& ctx);
329329

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

332332
// Iterates the known ciphers if the underlying implementation
333333
// is able to do so.
@@ -469,9 +469,9 @@ class Ec final {
469469
inline operator bool() const { return ec_ != nullptr; }
470470
inline operator OSSL3_CONST EC_KEY*() const { return ec_; }
471471

472-
static int GetCurveIdFromName(std::string_view name);
472+
static int GetCurveIdFromName(const char* name);
473473

474-
using GetCurveCallback = std::function<bool(std::string_view)>;
474+
using GetCurveCallback = std::function<bool(const char*)>;
475475
static bool GetCurves(GetCurveCallback callback);
476476

477477
private:
@@ -560,7 +560,7 @@ class BIOPointer final {
560560
static BIOPointer New(const BIO_METHOD* method);
561561
static BIOPointer New(const void* data, size_t len);
562562
static BIOPointer New(const BIGNUM* bn);
563-
static BIOPointer NewFile(std::string_view filename, std::string_view mode);
563+
static BIOPointer NewFile(const char* filename, const char* mode);
564564
static BIOPointer NewFp(FILE* fd, int flags);
565565

566566
template <typename T>
@@ -933,9 +933,8 @@ class DHPointer final {
933933
static BignumPointer GetStandardGenerator();
934934

935935
static BignumPointer FindGroup(
936-
const std::string_view name,
937-
FindGroupOption option = FindGroupOption::NONE);
938-
static DHPointer FromGroup(const std::string_view name,
936+
std::string_view name, FindGroupOption option = FindGroupOption::NONE);
937+
static DHPointer FromGroup(std::string_view name,
939938
FindGroupOption option = FindGroupOption::NONE);
940939

941940
static DHPointer New(BignumPointer&& p, BignumPointer&& g);
@@ -1034,7 +1033,7 @@ class SSLCtxPointer final {
10341033
SSL_CTX_set_tlsext_status_arg(get(), nullptr);
10351034
}
10361035

1037-
bool setCipherSuites(std::string_view ciphers);
1036+
bool setCipherSuites(const char* ciphers);
10381037

10391038
static SSLCtxPointer NewServer();
10401039
static SSLCtxPointer NewClient();
@@ -1063,8 +1062,8 @@ class SSLPointer final {
10631062
bool setSession(const SSLSessionPointer& session);
10641063
bool setSniContext(const SSLCtxPointer& ctx) const;
10651064

1066-
const std::string_view getClientHelloAlpn() const;
1067-
const std::string_view getClientHelloServerName() const;
1065+
const char* getClientHelloAlpn() const;
1066+
const char* getClientHelloServerName() const;
10681067

10691068
std::optional<const std::string_view> getServerName() const;
10701069
X509View getCertificate() const;
@@ -1080,7 +1079,7 @@ class SSLPointer final {
10801079

10811080
static std::optional<int> getSecurityLevel();
10821081

1083-
void getCiphers(std::function<void(const std::string_view)> cb) const;
1082+
void getCiphers(std::function<void(const char*)> cb) const;
10841083

10851084
static SSLPointer New(const SSLCtxPointer& ctx);
10861085
static std::optional<const std::string_view> GetServerName(const SSL* ssl);
@@ -1176,13 +1175,13 @@ class X509View final {
11761175
INVALID_NAME,
11771176
OPERATION_FAILED,
11781177
};
1179-
CheckMatch checkHost(const std::string_view host,
1178+
CheckMatch checkHost(std::string_view host,
11801179
int flags,
11811180
DataPointer* peerName = nullptr) const;
1182-
CheckMatch checkEmail(const std::string_view email, int flags) const;
1183-
CheckMatch checkIp(const std::string_view ip, int flags) const;
1181+
CheckMatch checkEmail(std::string_view email, int flags) const;
1182+
CheckMatch checkIp(std::string_view ip, int flags) const;
11841183

1185-
using UsageCallback = std::function<void(std::string_view)>;
1184+
using UsageCallback = std::function<void(const char*)>;
11861185
bool enumUsages(UsageCallback callback) const;
11871186

11881187
template <typename T>
@@ -1219,8 +1218,8 @@ class X509Pointer final {
12191218
X509View view() const;
12201219
operator X509View() const { return view(); }
12211220

1222-
static std::string_view ErrorCode(int32_t err);
1223-
static std::optional<std::string_view> ErrorReason(int32_t err);
1221+
static const char* ErrorCode(int32_t err);
1222+
static std::optional<const char*> ErrorReason(int32_t err);
12241223

12251224
private:
12261225
DeleteFnPtr<X509, X509_free> cert_;
@@ -1436,15 +1435,15 @@ class EnginePointer final {
14361435

14371436
bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr);
14381437
bool init(bool finish_on_exit = false);
1439-
EVPKeyPointer loadPrivateKey(const std::string_view key_name);
1438+
EVPKeyPointer loadPrivateKey(const char* key_name);
14401439

14411440
// Release ownership of the ENGINE* pointer.
14421441
ENGINE* release();
14431442

14441443
// Retrieve an OpenSSL Engine instance by name. If the name does not
14451444
// identify a valid named engine, the returned EnginePointer will be
14461445
// empty.
1447-
static EnginePointer getEngineByName(const std::string_view name,
1446+
static EnginePointer getEngineByName(const char* name,
14481447
CryptoErrorList* errors = nullptr);
14491448

14501449
// Call once when initializing OpenSSL at startup for the process.
@@ -1493,8 +1492,8 @@ Buffer<char> ExportChallenge(const char* input, size_t length);
14931492
// ============================================================================
14941493
// KDF
14951494

1496-
const EVP_MD* getDigestByName(const std::string_view name);
1497-
const EVP_CIPHER* getCipherByName(const std::string_view name);
1495+
const EVP_MD* getDigestByName(const char* name);
1496+
const EVP_CIPHER* getCipherByName(const char* name);
14981497

14991498
// Verify that the specified HKDF output length is valid for the given digest.
15001499
// 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());
@@ -939,7 +939,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
939939
Digest digest;
940940
if (args[offset + 2]->IsString()) {
941941
Utf8Value oaep_str(env->isolate(), args[offset + 2]);
942-
digest = Digest::FromName(oaep_str.ToStringView());
942+
digest = Digest::FromName(*oaep_str);
943943
if (!digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
944944
}
945945

0 commit comments

Comments
 (0)