diff options
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/compat/glibc_compat.cpp | 7 | ||||
-rw-r--r-- | src/compat/glibc_sanity.cpp | 45 | ||||
-rw-r--r-- | src/compat/sanity.h | 1 | ||||
-rw-r--r-- | src/i2p.cpp | 4 | ||||
-rw-r--r-- | src/i2p.h | 8 | ||||
-rw-r--r-- | src/init.cpp | 2 | ||||
-rw-r--r-- | src/netbase.cpp | 129 | ||||
-rw-r--r-- | src/netbase.h | 139 | ||||
-rw-r--r-- | src/qt/addressbookpage.cpp | 2 | ||||
-rw-r--r-- | src/qt/psbtoperationsdialog.cpp | 2 | ||||
-rw-r--r-- | src/qt/qrimagewidget.cpp | 4 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.cpp | 2 | ||||
-rw-r--r-- | src/qt/transactionview.cpp | 2 | ||||
-rw-r--r-- | src/qt/walletview.cpp | 2 | ||||
-rw-r--r-- | src/test/fuzz/parse_numbers.cpp | 3 | ||||
-rw-r--r-- | src/test/sanity_tests.cpp | 1 | ||||
-rw-r--r-- | src/test/sock_tests.cpp | 31 | ||||
-rw-r--r-- | src/test/util_tests.cpp | 59 | ||||
-rw-r--r-- | src/util/sock.cpp | 10 | ||||
-rw-r--r-- | src/util/sock.h | 5 |
21 files changed, 245 insertions, 214 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 8f37e055ff..a70ef65aa7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -553,7 +553,6 @@ libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ chainparamsbase.cpp \ clientversion.cpp \ - compat/glibc_sanity.cpp \ compat/glibcxx_sanity.cpp \ compat/strnlen.cpp \ fs.cpp \ diff --git a/src/compat/glibc_compat.cpp b/src/compat/glibc_compat.cpp index 8a51f310f7..ff581d4a9e 100644 --- a/src/compat/glibc_compat.cpp +++ b/src/compat/glibc_compat.cpp @@ -9,13 +9,6 @@ #include <cstddef> #include <cstdint> -// Prior to GLIBC_2.14, memcpy was aliased to memmove. -extern "C" void* memmove(void* a, const void* b, size_t c); -extern "C" void* memcpy(void* a, const void* b, size_t c) -{ - return memmove(a, b, c); -} - #if defined(__i386__) || defined(__arm__) extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp); diff --git a/src/compat/glibc_sanity.cpp b/src/compat/glibc_sanity.cpp deleted file mode 100644 index 06d0dd6fba..0000000000 --- a/src/compat/glibc_sanity.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2009-2020 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <cstddef> - -extern "C" void* memcpy(void* a, const void* b, size_t c); -void* memcpy_int(void* a, const void* b, size_t c) -{ - return memcpy(a, b, c); -} - -namespace -{ -// trigger: Use the memcpy_int wrapper which calls our internal memcpy. -// A direct call to memcpy may be optimized away by the compiler. -// test: Fill an array with a sequence of integers. memcpy to a new empty array. -// Verify that the arrays are equal. Use an odd size to decrease the odds of -// the call being optimized away. -template <unsigned int T> -bool sanity_test_memcpy() -{ - unsigned int memcpy_test[T]; - unsigned int memcpy_verify[T] = {}; - for (unsigned int i = 0; i != T; ++i) - memcpy_test[i] = i; - - memcpy_int(memcpy_verify, memcpy_test, sizeof(memcpy_test)); - - for (unsigned int i = 0; i != T; ++i) { - if (memcpy_verify[i] != i) - return false; - } - return true; -} -} // namespace - -bool glibc_sanity_test() -{ - return sanity_test_memcpy<1025>(); -} diff --git a/src/compat/sanity.h b/src/compat/sanity.h index 909c4f6da8..8efa416102 100644 --- a/src/compat/sanity.h +++ b/src/compat/sanity.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_COMPAT_SANITY_H #define BITCOIN_COMPAT_SANITY_H -bool glibc_sanity_test(); bool glibcxx_sanity_test(); #endif // BITCOIN_COMPAT_SANITY_H diff --git a/src/i2p.cpp b/src/i2p.cpp index 42270deaeb..d16c620d88 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -153,7 +153,7 @@ bool Session::Accept(Connection& conn) } const std::string& peer_dest = - conn.sock.RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt); + conn.sock.RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); @@ -252,7 +252,7 @@ Session::Reply Session::SendRequestAndGetReply(const Sock& sock, // signaled. static constexpr auto recv_timeout = 3min; - reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt); + reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE); for (const auto& kv : spanparsing::Split(reply.full, ' ')) { const auto& pos = std::find(kv.begin(), kv.end(), '='); @@ -41,6 +41,14 @@ struct Connection { namespace sam { /** + * The maximum size of an incoming message from the I2P SAM proxy (in bytes). + * Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator. + * The longest known message is ~1400 bytes, so this is high enough not to be triggered during + * normal operation, yet low enough to avoid a malicious proxy from filling our memory. + */ +static constexpr size_t MAX_MSG_SIZE{65536}; + +/** * I2P SAM session. */ class Session diff --git a/src/init.cpp b/src/init.cpp index d51f050f04..7d5420e3be 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -773,7 +773,7 @@ static bool InitSanityCheck() return InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); } - if (!glibc_sanity_test() || !glibcxx_sanity_test()) + if (!glibcxx_sanity_test()) return false; if (!Random_SanityCheck()) { diff --git a/src/netbase.cpp b/src/netbase.cpp index b95bb05e71..ac2392ebed 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -166,21 +166,6 @@ static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un return (vIP.size() > 0); } -/** - * Resolve a host string to its corresponding network addresses. - * - * @param name The string representing a host. Could be a name or a numerical - * IP address (IPv6 addresses in their bracketed form are - * allowed). - * @param[out] vIP The resulting network addresses to which the specified host - * string resolved. - * - * @returns Whether or not the specified host string successfully resolved to - * any resulting network addresses. - * - * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) - * for additional parameter descriptions. - */ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { @@ -196,12 +181,6 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function); } - /** - * Resolve a host string to its first corresponding network address. - * - * @see LookupHost(const std::string&, std::vector<CNetAddr>&, unsigned int, bool) for - * additional parameter descriptions. - */ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { @@ -215,26 +194,6 @@ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSL return true; } -/** - * Resolve a service string to its corresponding service. - * - * @param name The string representing a service. Could be a name or a - * numerical IP address (IPv6 addresses should be in their - * disambiguated bracketed form), optionally followed by a port - * number. (e.g. example.com:8333 or - * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) - * @param[out] vAddr The resulting services to which the specified service string - * resolved. - * @param portDefault The default port for resulting services if not specified - * by the service string. - * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, - * external queries may be performed. - * @param nMaxSolutions The maximum number of results we want, specifying 0 - * means "as many solutions as we get." - * - * @returns Whether or not the service string successfully resolved to any - * resulting services. - */ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function) { if (name.empty() || !ValidAsCString(name)) { @@ -254,12 +213,6 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefau return true; } -/** - * Resolve a service string to its first corresponding service. - * - * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) - * for additional parameter descriptions. - */ bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { @@ -273,16 +226,6 @@ bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllo return true; } -/** - * Resolve a service string with a numeric IP to its first corresponding - * service. - * - * @returns The resulting CService if the resolution was successful, [::]:0 - * otherwise. - * - * @see Lookup(const char *, CService&, int, bool) for additional parameter - * descriptions. - */ CService LookupNumeric(const std::string& name, int portDefault, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { @@ -420,24 +363,6 @@ static std::string Socks5ErrorString(uint8_t err) } } -/** - * Connect to a specified destination service through an already connected - * SOCKS5 proxy. - * - * @param strDest The destination fully-qualified domain name. - * @param port The destination port. - * @param auth The credentials with which to authenticate with the specified - * SOCKS5 proxy. - * @param sock The SOCKS5 proxy socket. - * - * @returns Whether or not the operation succeeded. - * - * @note The specified SOCKS5 proxy socket must already be connected to the - * SOCKS5 proxy. - * - * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol - * Version 5</a> - */ bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock) { IntrRecvError recvr; @@ -612,18 +537,6 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } -/** - * Try to connect to the specified service on the specified socket. - * - * @param addrConnect The service to which to connect. - * @param hSocket The socket on which to connect. - * @param nTimeout Wait this many milliseconds for the connection to be - * established. - * @param manual_connection Whether or not the connection was manually requested - * (e.g. through the addnode RPC) - * - * @returns Whether or not a connection was successfully made. - */ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection) { // Create a sockaddr from the specified service. @@ -722,22 +635,6 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { return true; } -/** - * Set the name proxy to use for all connections to nodes specified by a - * hostname. After setting this proxy, connecting to a node specified by a - * hostname won't result in a local lookup of said hostname, rather, connect to - * the node by asking the name proxy for a proxy connection to the hostname, - * effectively delegating the hostname lookup to the specified proxy. - * - * This delegation increases privacy for those who set the name proxy as they no - * longer leak their external hostname queries to their DNS servers. - * - * @returns Whether or not the operation succeeded. - * - * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 - * server in common use (most notably Tor) actually implements UDP - * support, and a DNS resolver is beyond the scope of this project. - */ bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) return false; @@ -768,21 +665,6 @@ bool IsProxy(const CNetAddr &addr) { return false; } -/** - * Connect to a specified destination service through a SOCKS5 proxy by first - * connecting to the SOCKS5 proxy. - * - * @param proxy The SOCKS5 proxy. - * @param strDest The destination service to which to connect. - * @param port The destination port. - * @param sock The socket on which to connect to the SOCKS5 proxy. - * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 - * proxy to be established. - * @param[out] outProxyConnectionFailed Whether or not the connection to the - * SOCKS5 proxy failed. - * - * @returns Whether or not the operation succeeded. - */ bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) { // first connect to proxy server @@ -806,17 +688,6 @@ bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int return true; } -/** - * Parse and resolve a specified subnet string into the appropriate internal - * representation. - * - * @param strSubnet A string representation of a subnet of the form `network - * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. - * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). - * @param ret The resulting internal representation of a subnet. - * - * @returns Whether the operation succeeded or not. - */ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(strSubnet)) { diff --git a/src/netbase.h b/src/netbase.h index 227da1a63b..e98a21ce1f 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -76,6 +76,22 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable = false); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); bool IsProxy(const CNetAddr &addr); +/** + * Set the name proxy to use for all connections to nodes specified by a + * hostname. After setting this proxy, connecting to a node specified by a + * hostname won't result in a local lookup of said hostname, rather, connect to + * the node by asking the name proxy for a proxy connection to the hostname, + * effectively delegating the hostname lookup to the specified proxy. + * + * This delegation increases privacy for those who set the name proxy as they no + * longer leak their external hostname queries to their DNS servers. + * + * @returns Whether or not the operation succeeded. + * + * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 + * server in common use (most notably Tor) actually implements UDP + * support, and a DNS resolver is beyond the scope of this project. + */ bool SetNameProxy(const proxyType &addrProxy); bool HaveNameProxy(); bool GetNameProxy(proxyType &nameProxyOut); @@ -83,11 +99,83 @@ bool GetNameProxy(proxyType &nameProxyOut); using DNSLookupFn = std::function<std::vector<CNetAddr>(const std::string&, bool)>; extern DNSLookupFn g_dns_lookup; +/** + * Resolve a host string to its corresponding network addresses. + * + * @param name The string representing a host. Could be a name or a numerical + * IP address (IPv6 addresses in their bracketed form are + * allowed). + * @param[out] vIP The resulting network addresses to which the specified host + * string resolved. + * + * @returns Whether or not the specified host string successfully resolved to + * any resulting network addresses. + * + * @see Lookup(const std::string&, std::vector<CService>&, int, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a host string to its first corresponding network address. + * + * @see LookupHost(const std::string&, std::vector<CNetAddr>&, unsigned int, bool, DNSLookupFn) + * for additional parameter descriptions. + */ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); -bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string to its corresponding service. + * + * @param name The string representing a service. Could be a name or a + * numerical IP address (IPv6 addresses should be in their + * disambiguated bracketed form), optionally followed by a port + * number. (e.g. example.com:8333 or + * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) + * @param[out] vAddr The resulting services to which the specified service string + * resolved. + * @param portDefault The default port for resulting services if not specified + * by the service string. + * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, + * external queries may be performed. + * @param nMaxSolutions The maximum number of results we want, specifying 0 + * means "as many solutions as we get." + * + * @returns Whether or not the service string successfully resolved to any + * resulting services. + */ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string to its first corresponding service. + * + * @see Lookup(const std::string&, std::vector<CService>&, int, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ +bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string with a numeric IP to its first corresponding + * service. + * + * @returns The resulting CService if the resolution was successful, [::]:0 otherwise. + * + * @see Lookup(const std::string&, std::vector<CService>&, int, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ CService LookupNumeric(const std::string& name, int portDefault = 0, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Parse and resolve a specified subnet string into the appropriate internal + * representation. + * + * @param strSubnet A string representation of a subnet of the form `network + * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. + * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). + * @param ret The resulting internal representation of a subnet. + * + * @returns Whether the operation succeeded or not. + */ bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet, DNSLookupFn dns_lookup_function = g_dns_lookup); /** @@ -102,14 +190,61 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family); */ extern std::function<std::unique_ptr<Sock>(const CService&)> CreateSock; -bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocketRet, int nTimeout, bool manual_connection); +/** + * Try to connect to the specified service on the specified socket. + * + * @param addrConnect The service to which to connect. + * @param hSocket The socket on which to connect. + * @param nTimeout Wait this many milliseconds for the connection to be + * established. + * @param manual_connection Whether or not the connection was manually requested + * (e.g. through the addnode RPC) + * + * @returns Whether or not a connection was successfully made. + */ +bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection); + +/** + * Connect to a specified destination service through a SOCKS5 proxy by first + * connecting to the SOCKS5 proxy. + * + * @param proxy The SOCKS5 proxy. + * @param strDest The destination service to which to connect. + * @param port The destination port. + * @param sock The socket on which to connect to the SOCKS5 proxy. + * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 + * proxy to be established. + * @param[out] outProxyConnectionFailed Whether or not the connection to the + * SOCKS5 proxy failed. + * + * @returns Whether or not the operation succeeded. + */ bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); + /** Disable or enable blocking-mode for a socket */ bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking); /** Set the TCP_NODELAY flag on a socket */ bool SetSocketNoDelay(const SOCKET& hSocket); void InterruptSocks5(bool interrupt); +/** + * Connect to a specified destination service through an already connected + * SOCKS5 proxy. + * + * @param strDest The destination fully-qualified domain name. + * @param port The destination port. + * @param auth The credentials with which to authenticate with the specified + * SOCKS5 proxy. + * @param sock The SOCKS5 proxy socket. + * + * @returns Whether or not the operation succeeded. + * + * @note The specified SOCKS5 proxy socket must already be connected to the + * SOCKS5 proxy. + * + * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol + * Version 5</a> + */ bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& socket); #endif // BITCOIN_NETBASE_H diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index ab6168a541..9927e925ac 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -295,7 +295,7 @@ void AddressBookPage::on_exportButton_clicked() // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Address List"), QString(), - tr("Comma separated file (*.csv)"), nullptr); + tr("Comma separated file", "Name of CSV file format") + QLatin1String(" (*.csv)"), nullptr); if (filename.isNull()) return; diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index d1f8d56100..17746b395b 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -141,7 +141,7 @@ void PSBTOperationsDialog::saveTransaction() { filename_suggestion.append(".psbt"); QString filename = GUIUtil::getSaveFileName(this, tr("Save Transaction Data"), filename_suggestion, - tr("Partially Signed Transaction (Binary) (*.psbt)"), &selected_filter); + tr("Partially Signed Transaction (Binary)", "Name of binary PSBT file format") + QLatin1String(" (*.psbt)"), &selected_filter); if (filename.isEmpty()) { return; } diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index 490826cbbb..a71c8831e9 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -120,7 +120,9 @@ void QRImageWidget::saveImage() { if (!GUIUtil::HasPixmap(this)) return; - QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); + QString fn = GUIUtil::getSaveFileName( + this, tr("Save QR Code"), QString(), + tr("PNG Image", "Name of PNG file format") + QLatin1String(" (*.png)"), nullptr); if (!fn.isEmpty()) { exportImage().save(fn); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index e401f46696..95e1ce2210 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -430,7 +430,7 @@ void SendCoinsDialog::on_sendButton_clicked() fileNameSuggestion.append(".psbt"); QString filename = GUIUtil::getSaveFileName(this, tr("Save Transaction Data"), fileNameSuggestion, - tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter); + tr("Partially Signed Transaction (Binary)", "Name of binary PSBT file format") + QLatin1String(" (*.psbt)"), &selectedFilter); if (filename.isEmpty()) { return; } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index f99a3e286d..42e08c6af7 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -357,7 +357,7 @@ void TransactionView::exportClicked() // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Transaction History"), QString(), - tr("Comma separated file (*.csv)"), nullptr); + tr("Comma separated file", "Name of CSV file format") + QLatin1String(" (*.csv)"), nullptr); if (filename.isNull()) return; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index b1e6b43e60..8612893683 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -273,7 +273,7 @@ void WalletView::backupWallet() { QString filename = GUIUtil::getSaveFileName(this, tr("Backup Wallet"), QString(), - tr("Wallet Data (*.dat)"), nullptr); + tr("Wallet Data", "Name of wallet data file format") + QLatin1String(" (*.dat)"), nullptr); if (filename.isEmpty()) return; diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp index ddd2bcfba3..1ad5fb6a05 100644 --- a/src/test/fuzz/parse_numbers.cpp +++ b/src/test/fuzz/parse_numbers.cpp @@ -18,6 +18,9 @@ FUZZ_TARGET(parse_numbers) double d; (void)ParseDouble(random_string, &d); + uint8_t u8; + (void)ParseUInt8(random_string, &u8); + int32_t i32; (void)ParseInt32(random_string, &i32); (void)atoi(random_string); diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 3e4b963fe3..496292875d 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -13,7 +13,6 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(basic_sanity) { - BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test"); BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index ed9780dfb5..400de875b7 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -4,11 +4,13 @@ #include <compat.h> #include <test/util/setup_common.h> +#include <threadinterrupt.h> #include <util/sock.h> #include <util/system.h> #include <boost/test/unit_test.hpp> +#include <cassert> #include <thread> using namespace std::chrono_literals; @@ -144,6 +146,35 @@ BOOST_AUTO_TEST_CASE(wait) waiter.join(); } +BOOST_AUTO_TEST_CASE(recv_until_terminator_limit) +{ + constexpr auto timeout = 1min; // High enough so that it is never hit. + CThreadInterrupt interrupt; + int s[2]; + CreateSocketPair(s); + + Sock sock_send(s[0]); + Sock sock_recv(s[1]); + + std::thread receiver([&sock_recv, &timeout, &interrupt]() { + constexpr size_t max_data{10}; + bool threw_as_expected{false}; + // BOOST_CHECK_EXCEPTION() writes to some variables shared with the main thread which + // creates a data race. So mimic it manually. + try { + sock_recv.RecvUntilTerminator('\n', timeout, interrupt, max_data); + } catch (const std::runtime_error& e) { + threw_as_expected = HasReason("too many bytes without a terminator")(e); + } + assert(threw_as_expected); + }); + + BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("1234567", timeout, interrupt)); + BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("89a\n", timeout, interrupt)); + + receiver.join(); +} + #endif /* WIN32 */ BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index b742fcaf0b..b1b71ef7c9 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -38,6 +38,7 @@ #include <boost/test/unit_test.hpp> using namespace std::literals; +static const std::string STRING_WITH_EMBEDDED_NULL_CHAR{"1"s "\0" "1"s}; /* defined in logging.cpp */ namespace BCLog { @@ -1272,7 +1273,7 @@ BOOST_AUTO_TEST_CASE(util_ParseMoney) // Parsing strings with embedded NUL characters should fail BOOST_CHECK(!ParseMoney("\0-1"s, ret)); - BOOST_CHECK(!ParseMoney("\0" "1"s, ret)); + BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR, ret)); BOOST_CHECK(!ParseMoney("1\0"s, ret)); } @@ -1450,9 +1451,7 @@ BOOST_AUTO_TEST_CASE(test_ParseInt32) BOOST_CHECK(!ParseInt32("aap", &n)); BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseInt32(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); BOOST_CHECK(!ParseInt32("2147483648", nullptr)); @@ -1480,9 +1479,7 @@ BOOST_AUTO_TEST_CASE(test_ParseInt64) BOOST_CHECK(!ParseInt64("1a", &n)); BOOST_CHECK(!ParseInt64("aap", &n)); BOOST_CHECK(!ParseInt64("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseInt64(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseInt64(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseInt64("9223372036854775808", nullptr)); @@ -1490,6 +1487,42 @@ BOOST_AUTO_TEST_CASE(test_ParseInt64) BOOST_CHECK(!ParseInt64("32482348723847471234", nullptr)); } +BOOST_AUTO_TEST_CASE(test_ParseUInt8) +{ + uint8_t n; + // Valid values + BOOST_CHECK(ParseUInt8("255", nullptr)); + BOOST_CHECK(ParseUInt8("0", &n) && n == 0); + BOOST_CHECK(ParseUInt8("255", &n) && n == 255); + BOOST_CHECK(ParseUInt8("0255", &n) && n == 255); // no octal + BOOST_CHECK(ParseUInt8("255", &n) && n == static_cast<uint8_t>(255)); + BOOST_CHECK(ParseUInt8("+255", &n) && n == 255); + BOOST_CHECK(ParseUInt8("00000000000000000012", &n) && n == 12); + BOOST_CHECK(ParseUInt8("00000000000000000000", &n) && n == 0); + // Invalid values + BOOST_CHECK(!ParseUInt8("-00000000000000000000", &n)); + BOOST_CHECK(!ParseUInt8("", &n)); + BOOST_CHECK(!ParseUInt8(" 1", &n)); // no padding inside + BOOST_CHECK(!ParseUInt8(" -1", &n)); + BOOST_CHECK(!ParseUInt8("++1", &n)); + BOOST_CHECK(!ParseUInt8("+-1", &n)); + BOOST_CHECK(!ParseUInt8("-+1", &n)); + BOOST_CHECK(!ParseUInt8("--1", &n)); + BOOST_CHECK(!ParseUInt8("-1", &n)); + BOOST_CHECK(!ParseUInt8("1 ", &n)); + BOOST_CHECK(!ParseUInt8("1a", &n)); + BOOST_CHECK(!ParseUInt8("aap", &n)); + BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex + BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex + BOOST_CHECK(!ParseUInt8(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); + // Overflow and underflow + BOOST_CHECK(!ParseUInt8("-255", &n)); + BOOST_CHECK(!ParseUInt8("256", &n)); + BOOST_CHECK(!ParseUInt8("-123", &n)); + BOOST_CHECK(!ParseUInt8("-123", nullptr)); + BOOST_CHECK(!ParseUInt8("256", nullptr)); +} + BOOST_AUTO_TEST_CASE(test_ParseUInt32) { uint32_t n; @@ -1519,9 +1552,7 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt32) BOOST_CHECK(!ParseUInt32("aap", &n)); BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseUInt32(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseUInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); BOOST_CHECK(!ParseUInt32("4294967296", &n)); @@ -1550,9 +1581,7 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt64) BOOST_CHECK(!ParseUInt64("1a", &n)); BOOST_CHECK(!ParseUInt64("aap", &n)); BOOST_CHECK(!ParseUInt64("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseUInt64(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseUInt64(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseUInt64("18446744073709551616", nullptr)); @@ -1582,9 +1611,7 @@ BOOST_AUTO_TEST_CASE(test_ParseDouble) BOOST_CHECK(!ParseDouble("1a", &n)); BOOST_CHECK(!ParseDouble("aap", &n)); BOOST_CHECK(!ParseDouble("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseDouble(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseDouble(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseDouble("-1e10000", nullptr)); BOOST_CHECK(!ParseDouble("1e10000", nullptr)); diff --git a/src/util/sock.cpp b/src/util/sock.cpp index e13c52a16a..f9ecfef5d4 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -175,7 +175,8 @@ void Sock::SendComplete(const std::string& data, std::string Sock::RecvUntilTerminator(uint8_t terminator, std::chrono::milliseconds timeout, - CThreadInterrupt& interrupt) const + CThreadInterrupt& interrupt, + size_t max_data) const { const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; std::string data; @@ -190,9 +191,14 @@ std::string Sock::RecvUntilTerminator(uint8_t terminator, // at a time is about 50 times slower. for (;;) { + if (data.size() >= max_data) { + throw std::runtime_error( + strprintf("Received too many bytes without a terminator (%u)", data.size())); + } + char buf[512]; - const ssize_t peek_ret{Recv(buf, sizeof(buf), MSG_PEEK)}; + const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; switch (peek_ret) { case -1: { diff --git a/src/util/sock.h b/src/util/sock.h index ecebb84205..4b0618dcff 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -135,13 +135,16 @@ public: * @param[in] terminator Character up to which to read from the socket. * @param[in] timeout Timeout for the entire operation. * @param[in] interrupt If this is signaled then the operation is canceled. + * @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes + * are received and there is still no terminator, then this method will throw an exception. * @return The data that has been read, without the terminating character. * @throws std::runtime_error if the operation cannot be completed. In this case some bytes may * have been consumed from the socket. */ virtual std::string RecvUntilTerminator(uint8_t terminator, std::chrono::milliseconds timeout, - CThreadInterrupt& interrupt) const; + CThreadInterrupt& interrupt, + size_t max_data) const; /** * Check if still connected. |