Skip to content

Commit 23b2adf

Browse files
tniessennpaun
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: nodejs/node#57816 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Darshan Sen <[email protected]>
1 parent 244cbac commit 23b2adf

File tree

3 files changed

+137
-41
lines changed

3 files changed

+137
-41
lines changed

include/ncrypto.h

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,11 @@ class Cipher final {
336336
int getKeyLength() const;
337337
int getBlockSize() const;
338338
std::string_view getModeLabel() const;
339-
std::string_view getName() const;
339+
const char* getName() const;
340340

341341
bool isSupportedAuthenticatedMode() const;
342342

343-
static const Cipher FromName(std::string_view name);
343+
static const Cipher FromName(const char* name);
344344
static const Cipher FromNid(int nid);
345345
static const Cipher FromCtx(const CipherCtxPointer& ctx);
346346

@@ -564,6 +564,10 @@ class Ec final {
564564
inline operator bool() const { return ec_ != nullptr; }
565565
inline operator OSSL3_CONST EC_KEY*() const { return ec_; }
566566

567+
static int GetCurveIdFromName(const char* name);
568+
569+
using GetCurveCallback = std::function<bool(const char*)>;
570+
static bool GetCurves(GetCurveCallback callback);
567571
inline const BignumPointer& getX() const { return x_; }
568572
inline const BignumPointer& getY() const { return y_; }
569573
inline const BignumPointer& getD() const { return d_; }
@@ -633,7 +637,7 @@ class BIOPointer final {
633637
static BIOPointer New(const BIO_METHOD* method);
634638
static BIOPointer New(const void* data, size_t len);
635639
static BIOPointer New(const BIGNUM* bn);
636-
static BIOPointer NewFile(std::string_view filename, std::string_view mode);
640+
static BIOPointer NewFile(const char* filename, const char* mode);
637641
static BIOPointer NewFp(FILE* fd, int flags);
638642

639643
template <typename T>
@@ -945,9 +949,8 @@ class DHPointer final {
945949
static BignumPointer GetStandardGenerator();
946950

947951
static BignumPointer FindGroup(
948-
const std::string_view name,
949-
FindGroupOption option = FindGroupOption::NONE);
950-
static DHPointer FromGroup(const std::string_view name,
952+
std::string_view name, FindGroupOption option = FindGroupOption::NONE);
953+
static DHPointer FromGroup(std::string_view name,
951954
FindGroupOption option = FindGroupOption::NONE);
952955

953956
static DHPointer New(BignumPointer&& p, BignumPointer&& g);
@@ -1046,6 +1049,8 @@ class SSLCtxPointer final {
10461049
SSL_CTX_set_tlsext_status_arg(get(), nullptr);
10471050
}
10481051

1052+
bool setCipherSuites(const char* ciphers);
1053+
10491054
static SSLCtxPointer NewServer();
10501055
static SSLCtxPointer NewClient();
10511056
static SSLCtxPointer New(const SSL_METHOD* method = TLS_method());
@@ -1073,8 +1078,8 @@ class SSLPointer final {
10731078
bool setSession(const SSLSessionPointer& session);
10741079
bool setSniContext(const SSLCtxPointer& ctx) const;
10751080

1076-
const std::string_view getClientHelloAlpn() const;
1077-
const std::string_view getClientHelloServerName() const;
1081+
const char* getClientHelloAlpn() const;
1082+
const char* getClientHelloServerName() const;
10781083

10791084
std::optional<const std::string_view> getServerName() const;
10801085
X509View getCertificate() const;
@@ -1087,8 +1092,9 @@ class SSLPointer final {
10871092
std::optional<std::string_view> getCipherVersion() const;
10881093

10891094
std::optional<uint32_t> verifyPeerCertificate() const;
1095+
static std::optional<int> getSecurityLevel();
10901096

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

10931099
static SSLPointer New(const SSLCtxPointer& ctx);
10941100
static std::optional<const std::string_view> GetServerName(const SSL* ssl);
@@ -1186,13 +1192,13 @@ class X509View final {
11861192
INVALID_NAME,
11871193
OPERATION_FAILED,
11881194
};
1189-
CheckMatch checkHost(const std::string_view host,
1195+
CheckMatch checkHost(std::string_view host,
11901196
int flags,
11911197
DataPointer* peerName = nullptr) const;
1192-
CheckMatch checkEmail(const std::string_view email, int flags) const;
1193-
CheckMatch checkIp(const std::string_view ip, int flags) const;
1198+
CheckMatch checkEmail(std::string_view email, int flags) const;
1199+
CheckMatch checkIp(std::string_view ip, int flags) const;
11941200

1195-
using UsageCallback = std::function<void(std::string_view)>;
1201+
using UsageCallback = std::function<void(const char*)>;
11961202
bool enumUsages(UsageCallback callback) const;
11971203

11981204
template <typename T>
@@ -1229,8 +1235,8 @@ class X509Pointer final {
12291235
X509View view() const;
12301236
operator X509View() const { return view(); }
12311237

1232-
static std::string_view ErrorCode(int32_t err);
1233-
static std::optional<std::string_view> ErrorReason(int32_t err);
1238+
static const char* ErrorCode(int32_t err);
1239+
static std::optional<const char*> ErrorReason(int32_t err);
12341240

12351241
private:
12361242
DeleteFnPtr<X509, X509_free> cert_;
@@ -1505,15 +1511,15 @@ class EnginePointer final {
15051511

15061512
bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr);
15071513
bool init(bool finish_on_exit = false);
1508-
EVPKeyPointer loadPrivateKey(const std::string_view key_name);
1514+
EVPKeyPointer loadPrivateKey(const char* key_name);
15091515

15101516
// Release ownership of the ENGINE* pointer.
15111517
ENGINE* release();
15121518

15131519
// Retrieve an OpenSSL Engine instance by name. If the name does not
15141520
// identify a valid named engine, the returned EnginePointer will be
15151521
// empty.
1516-
static EnginePointer getEngineByName(const std::string_view name,
1522+
static EnginePointer getEngineByName(const char* name,
15171523
CryptoErrorList* errors = nullptr);
15181524

15191525
// Call once when initializing OpenSSL at startup for the process.
@@ -1570,8 +1576,8 @@ DataPointer ExportChallenge(const Buffer<const char>& buf);
15701576
// ============================================================================
15711577
// KDF
15721578

1573-
const EVP_MD* getDigestByName(const std::string_view name);
1574-
const EVP_CIPHER* getCipherByName(const std::string_view name);
1579+
const EVP_MD* getDigestByName(const char* name);
1580+
const EVP_CIPHER* getCipherByName(const char* name);
15751581

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

src/engine.cpp

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() {

src/ncrypto.cpp

Lines changed: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) {
14191419
// When adding or removing errors below, please also update the list in the API
14201420
// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md
14211421
// Also *please* update the respective section in doc/api/tls.md as well
1422-
std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
1422+
const char* X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
14231423
#define CASE(CODE) \
14241424
case X509_V_ERR_##CODE: \
14251425
return #CODE;
@@ -1457,7 +1457,7 @@ std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int)
14571457
return "UNSPECIFIED";
14581458
}
14591459

1460-
std::optional<std::string_view> X509Pointer::ErrorReason(int32_t err) {
1460+
std::optional<const char*> X509Pointer::ErrorReason(int32_t err) {
14611461
if (err == X509_V_OK) return std::nullopt;
14621462
return X509_verify_cert_error_string(err);
14631463
}
@@ -1513,9 +1513,8 @@ BIOPointer BIOPointer::New(const void* data, size_t len) {
15131513
return BIOPointer(BIO_new_mem_buf(data, len));
15141514
}
15151515

1516-
BIOPointer BIOPointer::NewFile(std::string_view filename,
1517-
std::string_view mode) {
1518-
return BIOPointer(BIO_new_file(filename.data(), mode.data()));
1516+
BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) {
1517+
return BIOPointer(BIO_new_file(filename, mode));
15191518
}
15201519

15211520
BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) {
@@ -1797,17 +1796,17 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
17971796
// ============================================================================
17981797
// KDF
17991798

1800-
const EVP_MD* getDigestByName(const std::string_view name) {
1799+
const EVP_MD* getDigestByName(const char* name) {
18011800
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1
18021801
// exposed through the public API.
1803-
if (name == "dss1" || name == "DSS1") [[unlikely]] {
1802+
if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] {
18041803
return EVP_sha1();
18051804
}
1806-
return EVP_get_digestbyname(name.data());
1805+
return EVP_get_digestbyname(name);
18071806
}
18081807

1809-
const EVP_CIPHER* getCipherByName(const std::string_view name) {
1810-
return EVP_get_cipherbyname(name.data());
1808+
const EVP_CIPHER* getCipherByName(const char* name) {
1809+
return EVP_get_cipherbyname(name);
18111810
}
18121811

18131812
bool checkHkdfLength(const EVP_MD* md, size_t length) {
@@ -2920,8 +2919,7 @@ SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) {
29202919
return SSLPointer(SSL_new(ctx.get()));
29212920
}
29222921

2923-
void SSLPointer::getCiphers(
2924-
std::function<void(const std::string_view)> cb) const {
2922+
void SSLPointer::getCiphers(std::function<void(const char*)> cb) const {
29252923
if (!ssl_) return;
29262924
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get());
29272925

@@ -2986,7 +2984,7 @@ std::optional<uint32_t> SSLPointer::verifyPeerCertificate() const {
29862984
return std::nullopt;
29872985
}
29882986

2989-
const std::string_view SSLPointer::getClientHelloAlpn() const {
2987+
const char* SSLPointer::getClientHelloAlpn() const {
29902988
if (ssl_ == nullptr) return {};
29912989
#ifndef OPENSSL_IS_BORINGSSL
29922990
const unsigned char* buf;
@@ -3011,7 +3009,7 @@ const std::string_view SSLPointer::getClientHelloAlpn() const {
30113009
#endif
30123010
}
30133011

3014-
const std::string_view SSLPointer::getClientHelloServerName() const {
3012+
const char* SSLPointer::getClientHelloServerName() const {
30153013
if (ssl_ == nullptr) return {};
30163014
#ifndef OPENSSL_IS_BORINGSSL
30173015
const unsigned char* buf;
@@ -3154,6 +3152,12 @@ SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) {
31543152
bool SSLCtxPointer::setGroups(const char* groups) {
31553153
#ifndef NCRYPTO_NO_SSL_GROUP_LIST
31563154
return SSL_CTX_set1_groups_list(get(), groups) == 1;
3155+
}
3156+
3157+
bool SSLCtxPointer::setCipherSuites(const char* ciphers) {
3158+
#ifndef OPENSSL_IS_BORINGSSL
3159+
if (!ctx_) return false;
3160+
return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers);
31573161
#else
31583162
// Older versions of openssl/boringssl do not implement the
31593163
// SSL_CTX_set1_groups_list API
@@ -3163,8 +3167,8 @@ bool SSLCtxPointer::setGroups(const char* groups) {
31633167

31643168
// ============================================================================
31653169

3166-
const Cipher Cipher::FromName(std::string_view name) {
3167-
return Cipher(EVP_get_cipherbyname(name.data()));
3170+
const Cipher Cipher::FromName(const char* name) {
3171+
return Cipher(EVP_get_cipherbyname(name));
31683172
}
31693173

31703174
const Cipher Cipher::FromNid(int nid) {
@@ -3282,7 +3286,7 @@ std::string_view Cipher::getModeLabel() const {
32823286
return "{unknown}";
32833287
}
32843288

3285-
std::string_view Cipher::getName() const {
3289+
const char* Cipher::getName() const {
32863290
if (!cipher_) return {};
32873291
// OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of
32883292
// EVP_CIPHER_name(cipher) for compatibility with BoringSSL.
@@ -4193,6 +4197,74 @@ DataPointer Cipher::recover(const EVPKeyPointer& key,
41934197
key, params, in);
41944198
}
41954199

4200+
namespace {
4201+
struct CipherCallbackContext {
4202+
Cipher::CipherNameCallback cb;
4203+
void operator()(const char* name) { cb(name); }
4204+
};
4205+
4206+
#if OPENSSL_VERSION_MAJOR >= 3
4207+
template <class TypeName,
4208+
TypeName* fetch_type(OSSL_LIB_CTX*, const char*, const char*),
4209+
void free_type(TypeName*),
4210+
const TypeName* getbyname(const char*),
4211+
const char* getname(const TypeName*)>
4212+
void array_push_back(const TypeName* evp_ref,
4213+
const char* from,
4214+
const char* to,
4215+
void* arg) {
4216+
if (from == nullptr) return;
4217+
4218+
const TypeName* real_instance = getbyname(from);
4219+
if (!real_instance) return;
4220+
4221+
const char* real_name = getname(real_instance);
4222+
if (!real_name) return;
4223+
4224+
// EVP_*_fetch() does not support alias names, so we need to pass it the
4225+
// real/original algorithm name.
4226+
// We use EVP_*_fetch() as a filter here because it will only return an
4227+
// instance if the algorithm is supported by the public OpenSSL APIs (some
4228+
// algorithms are used internally by OpenSSL and are also passed to this
4229+
// callback).
4230+
TypeName* fetched = fetch_type(nullptr, real_name, nullptr);
4231+
if (fetched == nullptr) return;
4232+
4233+
free_type(fetched);
4234+
auto& cb = *(static_cast<CipherCallbackContext*>(arg));
4235+
cb(from);
4236+
}
4237+
#else
4238+
template <class TypeName>
4239+
void array_push_back(const TypeName* evp_ref,
4240+
const char* from,
4241+
const char* to,
4242+
void* arg) {
4243+
if (!from) return;
4244+
auto& cb = *(static_cast<CipherCallbackContext*>(arg));
4245+
cb(from);
4246+
}
4247+
#endif
4248+
} // namespace
4249+
4250+
void Cipher::ForEach(Cipher::CipherNameCallback callback) {
4251+
ClearErrorOnReturn clearErrorOnReturn;
4252+
CipherCallbackContext context;
4253+
context.cb = std::move(callback);
4254+
4255+
EVP_CIPHER_do_all_sorted(
4256+
#if OPENSSL_VERSION_MAJOR >= 3
4257+
array_push_back<EVP_CIPHER,
4258+
EVP_CIPHER_fetch,
4259+
EVP_CIPHER_free,
4260+
EVP_get_cipherbyname,
4261+
EVP_CIPHER_get0_name>,
4262+
#else
4263+
array_push_back<EVP_CIPHER>,
4264+
#endif
4265+
&context);
4266+
}
4267+
41964268
// ============================================================================
41974269

41984270
Ec::Ec() : ec_(nullptr) {}
@@ -4214,6 +4286,24 @@ int Ec::getCurve() const {
42144286
return EC_GROUP_get_curve_name(getGroup());
42154287
}
42164288

4289+
int Ec::GetCurveIdFromName(const char* name) {
4290+
int nid = EC_curve_nist2nid(name);
4291+
if (nid == NID_undef) {
4292+
nid = OBJ_sn2nid(name);
4293+
}
4294+
return nid;
4295+
}
4296+
4297+
bool Ec::GetCurves(Ec::GetCurveCallback callback) {
4298+
const size_t count = EC_get_builtin_curves(nullptr, 0);
4299+
std::vector<EC_builtin_curve> curves(count);
4300+
if (EC_get_builtin_curves(curves.data(), count) != count) {
4301+
return false;
4302+
}
4303+
for (auto curve : curves) {
4304+
if (!callback(OBJ_nid2sn(curve.nid))) return false;
4305+
}
4306+
return true;
42174307
uint32_t Ec::getDegree() const {
42184308
return EC_GROUP_get_degree(getGroup());
42194309
}

0 commit comments

Comments
 (0)