Skip to content

Commit e890fea

Browse files
committed
Add optional whitelist for added security
1 parent 963d53a commit e890fea

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

projects/premake5.lua

+11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ newoption({
44
value = "path to garrysmod_common directory"
55
})
66

7+
newoption({
8+
trigger = "whitelist",
9+
description = "Enables wrapping of getaddrinfo and a whitelist file to filter valid addresses and ports",
10+
value = "0 or 1"
11+
})
12+
713
local gmcommon = _OPTIONS.gmcommon or os.getenv("GARRYSMOD_COMMON")
14+
local whitelist = _OPTIONS.whitelist == "1"
815
if gmcommon == nil then
916
error("you didn't provide a path to your garrysmod_common (https://github.com/danielga/garrysmod_common) directory")
1017
end
@@ -16,6 +23,7 @@ local LUASOCKET_FOLDER = "../luasocket/src"
1623
CreateWorkspace({name = "socket.core"})
1724
CreateProject({serverside = true, manual_files = true})
1825
files("../source/socket.cpp")
26+
if whitelist then files("../source/whitelist.cpp") defines({"USE_WHITELIST"}) includedirs(LUASOCKET_FOLDER) end
1927
IncludeLuaShared()
2028
links("socket")
2129

@@ -24,6 +32,7 @@ CreateWorkspace({name = "socket.core"})
2432

2533
CreateProject({serverside = false, manual_files = true})
2634
files("../source/socket.cpp")
35+
if whitelist then files("../source/whitelist.cpp") defines({"USE_WHITELIST"}) includedirs(LUASOCKET_FOLDER) end
2736
IncludeLuaShared()
2837
links("socket")
2938

@@ -56,6 +65,7 @@ CreateWorkspace({name = "socket.core"})
5665
"LUASOCKET_API=__declspec(dllexport)",
5766
"MIME_API=__declspec(dllexport)"
5867
})
68+
if whitelist then defines({"getaddrinfo=__wrap_getaddrinfo"}) end
5969
files(LUASOCKET_FOLDER .. "/wsocket.c")
6070
links("ws2_32")
6171

@@ -65,6 +75,7 @@ CreateWorkspace({name = "socket.core"})
6575
"UNIX_API=''",
6676
"MIME_API=''"
6777
})
78+
if whitelist then defines({"getaddrinfo=__wrap_getaddrinfo"}) end
6879
files(LUASOCKET_FOLDER .. "/usocket.c")
6980

7081
CreateWorkspace({name = "mime.core"})

source/socket.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,33 @@
22

33
extern "C" int luaopen_socket_core( lua_State *state );
44

5+
int parseWhitelist();
6+
void clearWhitelist();
7+
enum : int
8+
{
9+
PARSE_SUCCESS = 0,
10+
PARSE_CANT_READ = 1,
11+
PARSE_NO_ENTRIES = 2
12+
};
13+
514
GMOD_MODULE_OPEN( )
615
{
16+
#ifdef USE_WHITELIST
17+
switch (parseWhitelist())
18+
{
19+
case PARSE_SUCCESS:
20+
break;
21+
case PARSE_CANT_READ:
22+
LUA->ThrowError("Failed to read whitelist file!");
23+
break;
24+
case PARSE_NO_ENTRIES:
25+
LUA->ThrowError("Didn't find any valid entries in whitelist file!");
26+
break;
27+
default:
28+
break;
29+
}
30+
#endif
31+
732
if( luaopen_socket_core( LUA->GetState( ) ) == 1 )
833
{
934
LUA->Push( -1 );
@@ -15,6 +40,10 @@ GMOD_MODULE_OPEN( )
1540

1641
GMOD_MODULE_CLOSE( )
1742
{
43+
#ifdef USE_WHITELIST
44+
clearWhitelist();
45+
#endif
46+
1847
LUA->PushNil( );
1948
LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "socket" );
2049
return 0;

source/whitelist.cpp

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#undef getaddrinfo
2+
3+
#include "socket.h"
4+
#include <vector>
5+
#include <map>
6+
#include <fstream>
7+
#include <sstream>
8+
#include <regex>
9+
10+
//Somewhere glua can't read?
11+
const char* whitelistDir = "../gm_socket_whitelist.txt";
12+
std::map<std::string, std::vector<std::regex> > whitelist;
13+
14+
enum : int
15+
{
16+
PARSE_SUCCESS = 0,
17+
PARSE_CANT_READ = 1,
18+
PARSE_NO_ENTRIES = 2
19+
};
20+
21+
int parseWhitelist()
22+
{
23+
std::ifstream input(whitelistDir);
24+
if (input)
25+
{
26+
std::stringstream filereader;
27+
filereader << input.rdbuf();
28+
std::string filedata = filereader.str();
29+
std::regex line_parser("(?:(?!\r?\n).)+");
30+
std::regex entry_parser("^[ \\t]*([\\w\\.\\*-]+)\\:(\\d+)[ \\t]*$");
31+
std::regex wildcard("\\*");
32+
std::regex dot("\\.");
33+
for (std::sregex_iterator line = std::sregex_iterator(filedata.begin(), filedata.end(), line_parser), end = std::sregex_iterator(); line != end; ++line)
34+
{
35+
const std::string& linestr = line->operator[](0);
36+
std::smatch match;
37+
if(std::regex_match(linestr, match, entry_parser))
38+
{
39+
std::string domain = match[1];
40+
domain = std::regex_replace(domain, wildcard, "[\\w-]+");
41+
domain = std::regex_replace(domain, dot, "\\.");
42+
whitelist[match[2].str()].push_back(std::regex(domain));
43+
}
44+
}
45+
if (whitelist.empty())
46+
{
47+
return PARSE_NO_ENTRIES;
48+
}
49+
}
50+
else
51+
{
52+
return PARSE_CANT_READ;
53+
}
54+
return PARSE_SUCCESS;
55+
}
56+
57+
void clearWhitelist()
58+
{
59+
whitelist.clear();
60+
}
61+
62+
bool isSafe(const char* pNodeName, const char* pServiceName)
63+
{
64+
std::map<std::string, std::vector<std::regex> >::iterator domains = whitelist.find(pServiceName);
65+
if (domains != whitelist.end())
66+
{
67+
for (auto i = domains->second.begin(), end = domains->second.end(); i != end; ++i)
68+
{
69+
if (std::regex_match(pNodeName, *i))
70+
{
71+
return true;
72+
}
73+
}
74+
return false;
75+
}
76+
else
77+
{
78+
return false;
79+
}
80+
}
81+
82+
extern "C" {
83+
84+
#ifdef _WIN32
85+
INT WSAAPI __wrap_getaddrinfo(
86+
_In_opt_ PCSTR pNodeName,
87+
_In_opt_ PCSTR pServiceName,
88+
_In_opt_ const ADDRINFOA * pHints,
89+
_Outptr_result_maybenull_ PADDRINFOA * ppResult
90+
)
91+
#else
92+
int __wrap_getaddrinfo (__const char *__restrict pNodeName,
93+
__const char *__restrict pServiceName,
94+
__const struct addrinfo *__restrict pHints,
95+
struct addrinfo **__restrict ppResult)
96+
#endif
97+
{
98+
if(isSafe(pNodeName, pServiceName))
99+
{
100+
return getaddrinfo(pNodeName, pServiceName, pHints, ppResult);
101+
}
102+
else
103+
{
104+
*ppResult = nullptr;
105+
return EAI_FAIL;
106+
}
107+
}
108+
109+
}

0 commit comments

Comments
 (0)