Skip to content

Commit

Permalink
Merge bitcoin#17754: net: Don't allow resolving of std::string with e…
Browse files Browse the repository at this point in the history
…mbedded NUL characters. Add tests.

7a046cd tests: Avoid using C-style NUL-terminated strings as arguments (practicalswift)
fefb916 tests: Add tests to make sure lookup methods fail on std::string parameters with embedded NUL characters (practicalswift)
9574de8 net: Avoid using C-style NUL-terminated strings as arguments in the netbase interface (practicalswift)

Pull request description:

  Don't allow resolving of `std::string`:s with embedded `NUL` characters.

  Avoid using C-style `NUL`-terminated strings as arguments in the `netbase` interface

  Add tests.

  The only place in where C-style `NUL`-terminated strings are actually needed is here:

  ```diff
  +    if (!ValidAsCString(name)) {
  +        return false;
  +    }
  ...
  -    int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
  +    int nErr = getaddrinfo(name.c_str(), nullptr, &aiHint, &aiRes);
       if (nErr)
           return false;
  ```

  Interface changes:

  ```diff
  -bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup);
  +bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup);

  -bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup);
  +bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup);

  -bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup);
  +bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup);

  -bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions);
  +bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions);

  -bool LookupSubNet(const char *pszName, CSubNet& subnet);
  +bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet);

  -CService LookupNumeric(const char *pszName, int portDefault = 0);
  +CService LookupNumeric(const std::string& name, int portDefault = 0);

  -bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocketRet, int nTimeout, bool *outProxyConnectionFailed);
  +bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocketRet, int nTimeout, bool& outProxyConnectionFailed);
  ```

  It should be noted that the `ConnectThroughProxy` change (from `bool *outProxyConnectionFailed` to `bool& outProxyConnectionFailed`) has nothing to do with `NUL` handling but I thought it was worth doing when touching this file :)

ACKs for top commit:
  EthanHeilman:
    ACK 7a046cd
  laanwj:
    ACK 7a046cd

Tree-SHA512: 66556e290db996917b54091acd591df221f72230f6b9f6b167b9195ee870ebef6e26f4cda2f6f54d00e1c362e1743bf56785d0de7cae854e6bf7d26f6caccaba
  • Loading branch information
laanwj authored and PastaPastaPasta committed Apr 7, 2022
1 parent e592537 commit bb5c622
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 69 deletions.
4 changes: 2 additions & 2 deletions src/httpserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ static bool InitHTTPAllowList()
rpc_allow_subnets.push_back(CSubNet(localv6)); // always allow IPv6 localhost
for (const std::string& strAllow : gArgs.GetArgs("-rpcallowip")) {
CSubNet subnet;
LookupSubNet(strAllow.c_str(), subnet);
LookupSubNet(strAllow, subnet);
if (!subnet.IsValid()) {
uiInterface.ThreadSafeMessageBox(
strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
Expand Down Expand Up @@ -325,7 +325,7 @@ static bool HTTPBindAddresses(struct evhttp* http)
evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle(http, i->first.empty() ? nullptr : i->first.c_str(), i->second);
if (bind_handle) {
CNetAddr addr;
if (i->first.empty() || (LookupHost(i->first.c_str(), addr, false) && addr.IsBindAny())) {
if (i->first.empty() || (LookupHost(i->first, addr, false) && addr.IsBindAny())) {
LogPrintf("WARNING: the RPC server is not safe to expose to untrusted networks such as the public internet\n");
}
boundSockets.push_back(bind_handle);
Expand Down
8 changes: 4 additions & 4 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ bool AppInitMain(InitInterfaces& interfaces)
SetReachable(NET_ONION, false);
if (proxyArg != "" && proxyArg != "0") {
CService proxyAddr;
if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) {
if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'").translated, proxyArg));
}

Expand All @@ -1836,7 +1836,7 @@ bool AppInitMain(InitInterfaces& interfaces)
SetReachable(NET_ONION, false);
} else {
CService onionProxy;
if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) {
if (!Lookup(onionArg, onionProxy, 9050, fNameLookup)) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'").translated, onionArg));
}
proxyType addrOnion = proxyType(onionProxy, proxyRandomize);
Expand All @@ -1854,7 +1854,7 @@ bool AppInitMain(InitInterfaces& interfaces)

for (const std::string& strAddr : gArgs.GetArgs("-externalip")) {
CService addrLocal;
if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
AddLocal(addrLocal, LOCAL_MANUAL);
else
return InitError(ResolveErrMsg("externalip", strAddr));
Expand Down Expand Up @@ -2432,7 +2432,7 @@ bool AppInitMain(InitInterfaces& interfaces)

for (const std::string& strBind : gArgs.GetArgs("-bind")) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) {
if (!Lookup(strBind, addrBind, GetListenPort(), false)) {
return InitError(ResolveErrMsg("bind", strBind));
}
connOptions.vBinds.push_back(addrBind);
Expand Down
9 changes: 5 additions & 4 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
connected = ConnectThroughProxy(proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket, nConnectTimeout, &proxyConnectionFailed);
connected = ConnectThroughProxy(proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket, nConnectTimeout, proxyConnectionFailed);
} else {
// no proxy needed (none set for target network)
hSocket = CreateSocket(addrConnect);
Expand All @@ -467,7 +467,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
std::string host;
int port = default_port;
SplitHostPort(std::string(pszDest), port, host);
connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, nullptr);
bool proxyConnectionFailed;
connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, proxyConnectionFailed);
}
if (!connected) {
CloseSocket(hSocket);
Expand Down Expand Up @@ -1968,7 +1969,7 @@ void CConnman::ThreadDNSAddressSeed()
continue;
}
unsigned int nMaxIPs = 256; // Limits number of IPs learned from a DNS seed
if (LookupHost(host.c_str(), vIPs, nMaxIPs, true)) {
if (LookupHost(host, vIPs, nMaxIPs, true)) {
for (const CNetAddr& ip : vIPs) {
int nOneDay = 24*3600;
CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits);
Expand Down Expand Up @@ -2288,7 +2289,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo()
}

for (const std::string& strAddNode : lAddresses) {
CService service(LookupNumeric(strAddNode.c_str(), Params().GetDefaultPort()));
CService service(LookupNumeric(strAddNode, Params().GetDefaultPort()));
AddedNodeInfo addedNode{strAddNode, CService(), false, false};
if (service.IsValid()) {
// strAddNode is an IP:port
Expand Down
4 changes: 2 additions & 2 deletions src/net_permissions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ bool NetWhitebindPermissions::TryParse(const std::string str, NetWhitebindPermis

const std::string strBind = str.substr(offset);
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false)) {
if (!Lookup(strBind, addrBind, 0, false)) {
error = ResolveErrMsg("whitebind", strBind);
return false;
}
Expand All @@ -94,7 +94,7 @@ bool NetWhitelistPermissions::TryParse(const std::string str, NetWhitelistPermis

const std::string net = str.substr(offset);
CSubNet subnet;
LookupSubNet(net.c_str(), subnet);
LookupSubNet(net, subnet);
if (!subnet.IsValid()) {
error = strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net);
return false;
Expand Down
77 changes: 48 additions & 29 deletions src/netbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

#include <sync.h>
#include <tinyformat.h>
#include <util/system.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>

#include <atomic>
#include <cstdint>
Expand Down Expand Up @@ -60,10 +61,14 @@ std::string GetNetworkName(enum Network net) {
}
}

bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
{
vIP.clear();

if (!ValidAsCString(name)) {
return false;
}

{
CNetAddr addr;
// From our perspective, onion addresses are not hostnames but rather
Expand All @@ -72,7 +77,7 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
// getaddrinfo to decode them and it wouldn't make sense to resolve
// them, we return a network address representing it instead. See
// CNetAddr::SetSpecial(const std::string&) for more details.
if (addr.SetSpecial(std::string(pszName))) {
if (addr.SetSpecial(name)) {
vIP.push_back(addr);
return true;
}
Expand All @@ -94,7 +99,7 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
// hostname lookups.
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
struct addrinfo *aiRes = nullptr;
int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
int nErr = getaddrinfo(name.c_str(), nullptr, &aiHint, &aiRes);
if (nErr)
return false;

Expand Down Expand Up @@ -132,7 +137,7 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
/**
* Resolve a host string to its corresponding network addresses.
*
* @param pszName The string representing a host. Could be a name or a numerical
* @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
Expand All @@ -144,28 +149,34 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
* for additional parameter descriptions.
*/
bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
{
std::string strHost(pszName);
if (!ValidAsCString(name)) {
return false;
}
std::string strHost = name;
if (strHost.empty())
return false;
if (strHost.front() == '[' && strHost.back() == ']') {
strHost = strHost.substr(1, strHost.size() - 2);
}

return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup);
return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup);
}

/**
* Resolve a host string to its first corresponding network address.
*
* @see LookupHost(const char *, std::vector<CNetAddr>&, unsigned int, bool) for
* @see LookupHost(const std::string&, std::vector<CNetAddr>&, unsigned int, bool) for
* additional parameter descriptions.
*/
bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup)
{
if (!ValidAsCString(name)) {
return false;
}
std::vector<CNetAddr> vIP;
LookupHost(pszName, vIP, 1, fAllowLookup);
LookupHost(name, vIP, 1, fAllowLookup);
if(vIP.empty())
return false;
addr = vIP.front();
Expand All @@ -175,7 +186,7 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
/**
* Resolve a service string to its corresponding service.
*
* @param pszName The string representing a service. Could be a name or a
* @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
Expand All @@ -192,16 +203,17 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
* @returns Whether or not the service string successfully resolved to any
* resulting services.
*/
bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions)
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions)
{
if (pszName[0] == 0)
if (name.empty() || !ValidAsCString(name)) {
return false;
}
int port = portDefault;
std::string hostname;
SplitHostPort(std::string(pszName), port, hostname);
SplitHostPort(name, port, hostname);

std::vector<CNetAddr> vIP;
bool fRet = LookupIntern(hostname.c_str(), vIP, nMaxSolutions, fAllowLookup);
bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup);
if (!fRet)
return false;
vAddr.resize(vIP.size());
Expand All @@ -216,10 +228,13 @@ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault,
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
* for additional parameter descriptions.
*/
bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup)
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup)
{
if (!ValidAsCString(name)) {
return false;
}
std::vector<CService> vService;
bool fRet = Lookup(pszName, vService, portDefault, fAllowLookup, 1);
bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1);
if (!fRet)
return false;
addr = vService[0];
Expand All @@ -236,12 +251,15 @@ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLoo
* @see Lookup(const char *, CService&, int, bool) for additional parameter
* descriptions.
*/
CService LookupNumeric(const char *pszName, int portDefault)
CService LookupNumeric(const std::string& name, int portDefault)
{
if (!ValidAsCString(name)) {
return {};
}
CService addr;
// "1.2:345" will fail to resolve the ip, but will still set the port.
// If the ip fails to resolve, re-init the result.
if(!Lookup(pszName, addr, portDefault, false))
if(!Lookup(name, addr, portDefault, false))
addr = CService();
return addr;
}
Expand Down Expand Up @@ -769,12 +787,11 @@ bool IsProxy(const CNetAddr &addr) {
*
* @returns Whether or not the operation succeeded.
*/
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed)
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool& outProxyConnectionFailed)
{
// first connect to proxy server
if (!ConnectSocketDirectly(proxy.proxy, hSocket, nTimeout, true)) {
if (outProxyConnectionFailed)
*outProxyConnectionFailed = true;
outProxyConnectionFailed = true;
return false;
}
// do socks negotiation
Expand All @@ -797,23 +814,25 @@ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int
* Parse and resolve a specified subnet string into the appropriate internal
* representation.
*
* @param pszName A string representation of a subnet of the form `network
* @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 char* pszName, CSubNet& ret)
bool LookupSubNet(const std::string& strSubnet, CSubNet& ret)
{
std::string strSubnet(pszName);
if (!ValidAsCString(strSubnet)) {
return false;
}
size_t slash = strSubnet.find_last_of('/');
std::vector<CNetAddr> vIP;

std::string strAddress = strSubnet.substr(0, slash);
// TODO: Use LookupHost(const char *, CNetAddr&, bool) instead to just get
// TODO: Use LookupHost(const std::string&, CNetAddr&, bool) instead to just get
// one CNetAddr.
if (LookupHost(strAddress.c_str(), vIP, 1, false))
if (LookupHost(strAddress, vIP, 1, false))
{
CNetAddr network = vIP[0];
if (slash != strSubnet.npos)
Expand All @@ -828,7 +847,7 @@ bool LookupSubNet(const char* pszName, CSubNet& ret)
else // If not a valid number, try full netmask syntax
{
// Never allow lookup for netmask
if (LookupHost(strNetmask.c_str(), vIP, 1, false)) {
if (LookupHost(strNetmask, vIP, 1, false)) {
ret = CSubNet(network, vIP[0]);
return ret.IsValid();
}
Expand Down
14 changes: 7 additions & 7 deletions src/netbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ bool IsProxy(const CNetAddr &addr);
bool SetNameProxy(const proxyType &addrProxy);
bool HaveNameProxy();
bool GetNameProxy(proxyType &nameProxyOut);
bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup);
bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup);
bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup);
bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions);
CService LookupNumeric(const char *pszName, int portDefault = 0);
bool LookupSubNet(const char *pszName, CSubNet& subnet);
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup);
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup);
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup);
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions);
CService LookupNumeric(const std::string& name, int portDefault = 0);
bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet);
SOCKET CreateSocket(const CService &addrConnect);
bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocketRet, int nTimeout, bool manual_connection);
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocketRet, int nTimeout, bool *outProxyConnectionFailed);
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocketRet, int nTimeout, bool& outProxyConnectionFailed);
/** Return readable error string for a network error code */
std::string NetworkErrorString(int err);
/** Close socket and set hSocket to INVALID_SOCKET */
Expand Down
2 changes: 1 addition & 1 deletion src/qt/optionsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) cons
{
Q_UNUSED(pos);
// Validate the proxy
CService serv(LookupNumeric(input.toStdString().c_str(), DEFAULT_GUI_PROXY_PORT));
CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT));
proxyType addrProxy = proxyType(serv, true);
if (addrProxy.IsValid())
return QValidator::Acceptable;
Expand Down
2 changes: 1 addition & 1 deletion src/qt/rpcconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1401,7 +1401,7 @@ void RPCConsole::unbanSelectedNode()
QString strNode = nodes.at(i).data().toString();
CSubNet possibleSubnet;

LookupSubNet(strNode.toStdString().c_str(), possibleSubnet);
LookupSubNet(strNode.toStdString(), possibleSubnet);
if (possibleSubnet.IsValid() && m_node.unban(possibleSubnet))
{
clientModel->getBanTableModel()->refresh();
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -617,11 +617,11 @@ static UniValue setban(const JSONRPCRequest& request)

if (!isSubnet) {
CNetAddr resolved;
LookupHost(request.params[0].get_str().c_str(), resolved, false);
LookupHost(request.params[0].get_str(), resolved, false);
netAddr = resolved;
}
else
LookupSubNet(request.params[0].get_str().c_str(), subNet);
LookupSubNet(request.params[0].get_str(), subNet);

if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) )
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Invalid IP/Subnet");
Expand Down
Loading

0 comments on commit bb5c622

Please sign in to comment.