1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation.  You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // shell.cc author Russ Combs <rucombs@cisco.com>
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "shell.h"
25 
26 #include <unistd.h>
27 
28 #include <cassert>
29 #include <fstream>
30 #include <stdexcept>
31 
32 #include "dump_config/config_output.h"
33 #include "log/messages.h"
34 #include "lua/lua.h"
35 #include "main/policy.h"
36 #include "main/snort_config.h"
37 #include "managers/module_manager.h"
38 #include "parser/parse_conf.h"
39 #include "parser/parser.h"
40 #include "utils/stats.h"
41 
42 #include "lua_bootstrap.h"
43 #include "lua_finalize.h"
44 
45 using namespace snort;
46 using namespace std;
47 
48 //-------------------------------------------------------------------------
49 // helper functions
50 //-------------------------------------------------------------------------
51 
52 static const char* versions[] = {
53     "SNORT_VERSION",
54     "SNORT_MAJOR_VERSION",
55     "SNORT_MINOR_VERSION",
56     "SNORT_PATCH_VERSION",
57     "SNORT_SUBLEVEL_VERSION",
58     nullptr
59 };
60 
install_version_strings(lua_State * L)61 static void install_version_strings(lua_State* L)
62 {
63     assert(versions[0]);
64 
65 #ifdef BUILD
66     lua_pushstring(L, VERSION "-" BUILD);
67 #else
68     lua_pushstring(L, VERSION);
69 #endif
70     lua_setglobal(L, versions[0]);
71 
72     std::istringstream vs(VERSION);
73     for ( int i = 1 ; versions[i] ; i++ )
74     {
75         std::string tmp;
76         int num = 0;
77         std::getline(vs, tmp, '.');
78 
79         if ( !tmp.empty() )
80             num = stoi(tmp);
81 
82         lua_pushinteger(L, num);
83         lua_setglobal(L, versions[i]);
84     }
85 }
86 
87 string Shell::fatal;
88 std::stack<Shell*> Shell::current_shells;
89 ConfigOutput* Shell::s_config_output = nullptr;
90 BaseConfigNode* Shell::s_current_node = nullptr;
91 bool Shell::s_close_table = true;
92 string Shell::lua_sandbox;
93 
94 // FIXIT-M Shell::panic() works on Linux but on OSX we can't throw from lua
95 // to C++.  unprotected lua calls could be wrapped in a pcall to ensure lua
96 // panics don't kill the process.  or we can not use lua for the shell.  :(
panic(lua_State * L)97 [[noreturn]] int Shell::panic(lua_State* L)
98 {
99     fatal = lua_tostring(L, -1);
100     throw runtime_error(fatal);
101 }
102 
get_current_shell()103 Shell* Shell::get_current_shell()
104 {
105     if ( !current_shells.empty() )
106         return current_shells.top();
107 
108     return nullptr;
109 }
110 
set_config_output(ConfigOutput * output)111 void Shell::set_config_output(ConfigOutput* output)
112 { s_config_output = output; }
113 
clear_config_output()114 void Shell::clear_config_output()
115 {
116     s_config_output = nullptr;
117     s_current_node = nullptr;
118 }
119 
is_trusted(const std::string & key)120 bool Shell::is_trusted(const std::string& key)
121 {
122     Shell* sh = Shell::get_current_shell();
123 
124     if ( !sh )
125         return false;
126 
127     const Allowlist& allowlist = sh->get_allowlist();
128     const Allowlist& internal_allowlist = sh->get_internal_allowlist();
129     const Allowlist& allowlist_prefixes = sh->get_allowlist_prefixes();
130 
131     for ( const auto& prefix : allowlist_prefixes )
132     {
133         if (key.compare(0, prefix.length(), prefix) == 0)
134             return true;
135     }
136 
137     if ( allowlist.find(key) != allowlist.end() )
138         return true;
139 
140     if ( internal_allowlist.find(key) != internal_allowlist.end() )
141         return true;
142 
143     return false;
144 }
145 
allowlist_append(const char * keyword,bool is_prefix)146 void Shell::allowlist_append(const char* keyword, bool is_prefix)
147 {
148     Shell* sh = Shell::get_current_shell();
149 
150     if ( !sh )
151         return;
152 
153     sh->allowlist_update(keyword, is_prefix);
154 }
155 
config_open_table(bool is_root_node,bool is_list,int idx,const std::string & table_name,const Parameter * p)156 void Shell::config_open_table(bool is_root_node, bool is_list, int idx,
157     const std::string& table_name, const Parameter* p)
158 {
159     if ( !s_config_output )
160         return;
161 
162     Parameter::Type node_type = is_list ? Parameter::PT_LIST : Parameter::PT_TABLE;
163     if ( is_root_node )
164         add_config_root_node(table_name, node_type);
165     else
166     {
167         if ( p )
168             node_type = p->type;
169 
170         if ( node_type == Parameter::PT_MULTI )
171         {
172             s_close_table = false;
173             return;
174         }
175 
176         if ( node_type == Parameter::PT_TABLE )
177             update_current_config_node(table_name);
178         else
179         {
180             if ( idx )
181                 node_type = Parameter::PT_TABLE;
182 
183             add_config_child_node(table_name, node_type, idx);
184         }
185     }
186 }
187 
add_config_child_node(const std::string & node_name,snort::Parameter::Type type,bool is_root_list_item)188 void Shell::add_config_child_node(const std::string& node_name, snort::Parameter::Type type,
189     bool is_root_list_item)
190 {
191     if ( !s_config_output || !s_current_node )
192         return;
193 
194     // element of the top-level list is anonymous
195     std::string name = ( !is_root_list_item && s_current_node->get_name() != node_name ) ?
196         node_name : "";
197 
198     auto new_node = new TreeConfigNode(s_current_node, name, type);
199     s_current_node->add_child_node(new_node);
200     s_current_node = new_node;
201 }
202 
add_config_root_node(const std::string & root_name,snort::Parameter::Type node_type)203 void Shell::add_config_root_node(const std::string& root_name, snort::Parameter::Type node_type)
204 {
205     if ( !s_config_output )
206         return;
207 
208     Shell* sh = Shell::get_current_shell();
209 
210     if ( !sh )
211         return;
212 
213     sh->s_current_node = new TreeConfigNode(nullptr, root_name, node_type);
214     sh->config_data.add_config_tree(sh->s_current_node);
215 }
216 
update_current_config_node(const std::string & node_name)217 void Shell::update_current_config_node(const std::string& node_name)
218 {
219     if ( !s_config_output || !s_current_node )
220         return;
221 
222     // node has been added during setting default options
223     if ( !node_name.empty() )
224         s_current_node = s_current_node->get_node(node_name);
225     else if ( s_current_node->get_parent_node() and
226         s_current_node->get_type() == Parameter::PT_TABLE and
227         !s_current_node->get_name().empty() )
228             s_current_node = s_current_node->get_parent_node();
229 
230     assert(s_current_node);
231 }
232 
config_close_table()233 void Shell::config_close_table()
234 {
235     if ( !s_config_output )
236         return;
237 
238     if ( !s_close_table )
239     {
240         s_close_table = true;
241         return;
242     }
243 
244     if ( !s_current_node )
245         return;
246 
247     s_current_node = s_current_node->get_parent_node();
248 }
249 
set_config_value(const std::string & fqn,const snort::Value & value)250 void Shell::set_config_value(const std::string& fqn, const snort::Value& value)
251 {
252     if ( !s_config_output || !s_current_node )
253         return;
254 
255     // don't give names to list elements
256     if ( s_current_node->get_type() == Parameter::PT_LIST )
257     {
258         s_current_node->add_child_node(new ValueConfigNode(s_current_node, value, ""));
259         return;
260     }
261 
262     BaseConfigNode* child_node = nullptr;
263 
264     std::string custom_name;
265     if ( strchr(value.get_name(), '$') )
266         custom_name = fqn.substr(fqn.find_last_of(".") + 1);
267 
268     for ( auto node : s_current_node->get_children() )
269     {
270         if ( (node->get_type() == Parameter::PT_MULTI) and (node->get_name() == value.get_name()) )
271         {
272             child_node = node;
273             break;
274         }
275 
276         if ( !custom_name.empty() )
277             child_node = node->get_node(custom_name);
278         else
279             child_node = node->get_node(value.get_name());
280 
281         if ( child_node )
282             break;
283     }
284 
285     if ( !child_node )
286         s_current_node->add_child_node(new ValueConfigNode(s_current_node, value, custom_name));
287     else
288         child_node->set_value(value);
289 }
290 
291 // FIXIT-L shell --pause should stop before loading config so Lua state
292 // can be examined and modified.
293 
294 #if 0
295 // :( it does not look possible to get file and line after load
296 static int get_line_number(lua_State* L)
297 {
298     lua_Debug ar;
299     lua_getstack(L, 1, &ar);
300     lua_getinfo(L, "nSl", &ar);
301     return ar.currentline;
302 }
303 
304 #endif
305 
set_sandbox_env()306 bool Shell::set_sandbox_env()
307 {
308     lua_getglobal(lua, "sandbox_env");
309 
310     if ( lua_istable(lua, -1) )
311     {
312         if ( !lua_setfenv(lua, -2) )
313         {
314             ParseError("can't set sandbox environment\n");
315             return false;
316         }
317     }
318     else
319     {
320         ParseError("sandbox environment not defined\n");
321         return false;
322     }
323     return true;
324 }
325 
load_lua_sandbox()326 bool Shell::load_lua_sandbox()
327 {
328     Lua::ManageStack ms(lua);
329 
330     LogMessage("Loading lua sandbox %s:\n", lua_sandbox.c_str());
331     if ( luaL_loadfile(lua, lua_sandbox.c_str()) )
332     {
333         ParseError("can't load lua sandbox %s: %s\n", lua_sandbox.c_str(), lua_tostring(lua, -1));
334         return false;
335     }
336 
337     if ( lua_pcall(lua, 0, 0, 0) )
338     {
339         ParseError("can't init lua sandbox %s: %s\n", lua_sandbox.c_str(), lua_tostring(lua, -1));
340         return false;
341     }
342     LogMessage("Finished %s:\n", lua_sandbox.c_str());
343 
344     lua_getglobal(lua, "sandbox_env");
345     if ( !lua_istable(lua, -1) )
346     {
347         ParseError("sandbox_env table doesn't exist in %s: %s\n", lua_sandbox.c_str(),
348             lua_tostring(lua, -1));
349         return false;
350     }
351 
352     lua_getglobal(lua, "create_sandbox_env");
353     if ( lua_pcall(lua, 0, 0, 0) != 0 )
354     {
355         ParseError("can't create sandbox environment %s: %s\n", lua_sandbox.c_str(),
356             lua_tostring(lua, -1));
357         return false;
358     }
359 
360     return true;
361 }
362 
load_string(const char * s,bool load_in_sandbox,const char * message)363 bool Shell::load_string(const char* s, bool load_in_sandbox, const char* message)
364 {
365     Lua::ManageStack ms(lua);
366 
367     if ( luaL_loadstring(lua, s) )
368     {
369         ParseError("can't load %s: %s\n", message, lua_tostring(lua, -1));
370         return false;
371     }
372 
373     if ( load_in_sandbox && !set_sandbox_env() )
374         return false;
375 
376 
377     if ( lua_pcall(lua, 0, 0, 0) )
378     {
379         ParseError("can't init %s: %s\n", message, lua_tostring(lua, -1));
380         return false;
381     }
382 
383     return true;
384 }
385 
load_config(const char * file,bool load_in_sandbox)386 bool Shell::load_config(const char* file, bool load_in_sandbox)
387 {
388     if ( load_in_sandbox )
389     {
390         ifstream in_file(file, ifstream::in);
391         if (in_file.get() == 27 )
392         {
393             ParseError("bytecode is not allowed %s\n", file);
394             return false;
395         }
396     }
397 
398     Lua::ManageStack ms(lua);
399 
400     if ( luaL_loadfile(lua, file) )
401     {
402         ParseError("can't load %s: %s\n", file, lua_tostring(lua, -1));
403         return false;
404     }
405 
406     if ( load_in_sandbox && !set_sandbox_env() )
407         return false;
408 
409     if ( lua_pcall(lua, 0, 0, 0) )
410     {
411         ParseError("can't init %s: %s\n", file, lua_tostring(lua, -1));
412         return false;
413     }
414 
415     return true;
416 }
417 
418 //-------------------------------------------------------------------------
419 // public methods
420 //-------------------------------------------------------------------------
421 
Shell(const char * s,bool load_defaults)422 Shell::Shell(const char* s, bool load_defaults) :
423     config_data(s), load_defaults(load_defaults)
424 {
425     // FIXIT-M should wrap in Lua::State
426     lua = luaL_newstate();
427 
428     if ( !lua )
429         FatalError("Lua state instantiation failed\n");
430 
431     current_shells.push(this);
432 
433     lua_atpanic(lua, Shell::panic);
434     luaL_openlibs(lua);
435 
436     if ( s )
437         file = s;
438 
439     parse_from = get_parse_file();
440 
441     loaded = false;
442     load_string(lua_bootstrap, false, "bootstrap");
443     install_version_strings(lua);
444     bootstrapped = true;
445 
446     current_shells.pop();
447 }
448 
~Shell()449 Shell::~Shell()
450 {
451     lua_close(lua);
452 }
453 
set_file(const char * s)454 void Shell::set_file(const char* s)
455 {
456     file = s;
457     config_data.file_name = file;
458 }
459 
set_overrides(const char * s)460 void Shell::set_overrides(const char* s)
461 {
462     overrides += s;
463 }
464 
set_overrides(Shell * sh)465 void Shell::set_overrides(Shell* sh)
466 {
467     overrides += sh->overrides;
468 }
469 
configure(SnortConfig * sc,bool is_root)470 bool Shell::configure(SnortConfig* sc, bool is_root)
471 {
472     assert(file.size());
473     ModuleManager::set_config(sc);
474 
475     //set_*_policy can set to null. this is used
476     //to tell which pieces to pick from sub policy
477     auto pt = sc->policy_map->get_policies(this);
478     if ( pt.get() == nullptr )
479         set_default_policy(sc);
480     else
481     {
482         set_inspection_policy(pt->inspection);
483         set_ips_policy(pt->ips);
484         set_network_policy(pt->network);
485     }
486 
487     if (!sc->tweaks.empty())
488     {
489         lua_pushstring(lua, sc->tweaks.c_str());
490         lua_setglobal(lua, "tweaks");
491     }
492 
493     bool load_in_sandbox = true;
494 
495     if ( lua_sandbox.empty() )
496         load_in_sandbox = false;
497     else if ( !load_lua_sandbox() )
498         return false;
499 
500     if ( load_defaults )
501         load_string(ModuleManager::get_lua_coreinit(), load_in_sandbox, "coreinit");
502 
503     std::string path = parse_from;
504     const char* code;
505 
506     if ( !is_root )
507         code = get_config_file(file.c_str(), path);
508     else
509     {
510         code = "W";
511         path = file;
512     }
513 
514     if ( !code )
515     {
516         ParseError("can't find %s\n", file.c_str());
517         return false;
518     }
519 
520     push_parse_location(code, path.c_str(), file.c_str(), 0);
521 
522     current_shells.push(this);
523 
524     if ( !path.empty() and
525         !load_config(path.c_str(), load_in_sandbox) )
526     {
527         current_shells.pop();
528         return false;
529     }
530 
531     if ( !overrides.empty() )
532         load_string(overrides.c_str(), load_in_sandbox, "overrides");
533 
534     if ( SnortConfig::log_verbose() )
535         print_allowlist();
536 
537     load_string(lua_finalize, false, "finalize");
538 
539     clear_allowlist();
540 
541     auto config_output = Shell::get_current_shell()->s_config_output;
542     if ( config_output )
543         config_output->dump_config(config_data);
544 
545     current_shells.pop();
546 
547     set_default_policy(sc);
548     ModuleManager::set_config(nullptr);
549     loaded = true;
550 
551     pop_parse_location();
552 
553     return true;
554 }
555 
install(const char * name,const luaL_Reg * reg)556 void Shell::install(const char* name, const luaL_Reg* reg)
557 {
558     if ( !strcmp(name, "snort") )
559         luaL_register(lua, "_G", reg);
560 
561     luaL_register(lua, name, reg);
562 }
563 
execute(const char * cmd,string & rsp)564 void Shell::execute(const char* cmd, string& rsp)
565 {
566     int err = 0;
567     Lua::ManageStack ms(lua);
568 
569     try
570     {
571         // FIXIT-L shares logic with chunk
572         err = luaL_loadbuffer(lua, cmd, strlen(cmd), "shell");
573 
574         if ( !err )
575             err = lua_pcall(lua, 0, 0, 0);
576     }
577     catch (...)
578     {
579         rsp = fatal;
580     }
581 
582     if (err)
583     {
584         rsp = lua_tostring(lua, -1);
585         rsp += "\n";
586         lua_pop(lua, 1);
587     }
588 }
589 
590 //-------------------------------------------------------------------------
591 // Helper methods
592 //-------------------------------------------------------------------------
593 
print_list(const Shell::Allowlist & wlist,const std::string & msg)594 static void print_list(const Shell::Allowlist& wlist, const std::string& msg)
595 {
596     LogMessage("\t%s\n", msg.c_str());
597     std::string list;
598 
599     for ( const auto& wl : wlist )
600     {
601         list += wl;
602         list += ", ";
603     }
604 
605     if ( !list.empty() )
606         list.erase(list.end() - 2, list.end());
607 
608     ConfigLogger::log_list(list.c_str());
609 }
610 
611 //-------------------------------------------------------------------------
612 // private methods
613 //-------------------------------------------------------------------------
614 
print_allowlist() const615 void Shell::print_allowlist() const
616 {
617     std::string output;
618     if ( !allowlist.empty() )
619     {
620         output = "Lua Allowlist Keywords for " + file + ":";
621         print_list(allowlist, output);
622     }
623 
624     if ( !allowlist_prefixes.empty() )
625     {
626         output = "Lua Allowlist Prefixes for " + file + ":";
627         print_list(allowlist_prefixes, output);
628     }
629 }
630 
allowlist_update(const char * s,bool is_prefix)631 void Shell::allowlist_update(const char* s, bool is_prefix)
632 {
633     Allowlist* wlist = nullptr;
634     if ( is_prefix )
635         wlist = &allowlist_prefixes;
636     else if ( !bootstrapped )
637         wlist = &internal_allowlist;
638     else
639         wlist = &allowlist;
640 
641     if ( s )
642         wlist->emplace(s);
643 }
644 
645