From fdbef979b613c5204eefc94d8c1b448ffcf3aab0 Mon Sep 17 00:00:00 2001 From: Michal Rakowski Date: Sat, 16 Oct 2021 13:09:24 +0200 Subject: [PATCH] Add 'allowed scripts directory' directory for the FD --- bacula/src/filed/fd_plugins.c | 15 ++++++-- bacula/src/filed/fd_snapshot.c | 5 ++- bacula/src/filed/job.c | 23 ++++++++----- bacula/src/filed/protos.h | 2 +- bacula/src/jcr.h | 2 ++ bacula/src/lib/bpipe.c | 62 +++++++++++++++++++++++++++++++--- 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/bacula/src/filed/fd_plugins.c b/bacula/src/filed/fd_plugins.c index 54c859ac5..88e13f411 100644 --- a/bacula/src/filed/fd_plugins.c +++ b/bacula/src/filed/fd_plugins.c @@ -2310,7 +2310,10 @@ static bRC baculaAddExclude(bpContext *ctx, const char *file) /* Set the Exclude context */ set_incexe(jcr, bctx->exclude); - add_file_to_fileset(jcr, file, true); + if (add_file_to_fileset(jcr, file, true) != state_include) { + Dmsg1(100, "Failed to add exclude file=%s\n", file); + return bRC_Error; + } /* Restore the current context */ set_incexe(jcr, old); @@ -2350,7 +2353,10 @@ static bRC baculaAddInclude(bpContext *ctx, const char *file) } set_incexe(jcr, bctx->include); - add_file_to_fileset(jcr, file, true); + if (add_file_to_fileset(jcr, file, true) != state_include) { + Dmsg1(100, "Failed to add include file=%s\n", file); + return bRC_Error; + } /* Restore the current context */ set_incexe(jcr, old); @@ -2414,7 +2420,10 @@ static bRC baculaAddPlugin(bpContext *ctx, const char *file) } set_incexe(jcr, bctx->include); - add_file_to_fileset(jcr, file, false); + if (add_file_to_fileset(jcr, file, false) != state_include) { + Dmsg1(100, "Failed to plugin=%s\n", file); + return bRC_Error; + } /* Restore the current context */ set_incexe(jcr, old); diff --git a/bacula/src/filed/fd_snapshot.c b/bacula/src/filed/fd_snapshot.c index 980b1c1b2..1e4b40838 100644 --- a/bacula/src/filed/fd_snapshot.c +++ b/bacula/src/filed/fd_snapshot.c @@ -1495,8 +1495,11 @@ bool snapshot_manager::create_snapshots() set_incexe(jcr, exclude); } Mmsg(t, "%s", elt->snap->SnapDirectory); + if (add_file_to_fileset(jcr, t.c_str(), true) != state_include) { + Jmsg(jcr, M_ERROR, 0, " Failed to exclude=%s\n", t.c_str()); + return false; + } Dmsg1(DT_SNAPSHOT|10, "Excluding %s\n", t.c_str()); - add_file_to_fileset(jcr, t.c_str(), true); } } } diff --git a/bacula/src/filed/job.c b/bacula/src/filed/job.c index 0fda15eff..c91caba82 100644 --- a/bacula/src/filed/job.c +++ b/bacula/src/filed/job.c @@ -403,6 +403,12 @@ static void *handle_director_request(BSOCK *dir) quit = true; break; } + + /* Set per-director list of allowed directories for scripts/program being run */ + if (jcr->director && jcr->director->allowed_script_dirs) { + jcr->allowed_script_dirs = jcr->director->allowed_script_dirs; + } + Dmsg1(100, "Executing Dir %s command.\n", dir->msg); if (!cmds[i].func(jcr)) { /* do command */ quit = true; /* error or fully terminated, get out */ @@ -1455,7 +1461,7 @@ static void append_file(JCR *jcr, findINCEXE *incexe, * Add fname to include/exclude fileset list. First check for * | and < and if necessary perform command. */ -void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) +int add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) { findFILESET *fileset = jcr->ff->fileset; char *p; @@ -1480,7 +1486,7 @@ void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"), p, be.bstrerror()); free_pool_memory(fn); - return; + return state_error; } free_pool_memory(fn); while (fgets(buf, sizeof(buf), bpipe->rfd)) { @@ -1493,7 +1499,7 @@ void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) berrno be; Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. stat=%d: ERR=%s\n"), p, be.code(stat), be.bstrerror(stat)); - return; + return state_error; } break; case '<': @@ -1504,7 +1510,7 @@ void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) Jmsg(jcr, M_FATAL, 0, _("Cannot open FileSet input file: %s. ERR=%s\n"), p, be.bstrerror()); - return; + return state_error; } while (fgets(buf, sizeof(buf), ffd)) { strip_trailing_junk(buf); @@ -1516,6 +1522,9 @@ void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file) append_file(jcr, fileset->incexe, fname, is_file); break; } + + //TODO check all calls for add_file_to_fileset() to check for ret code now + return state_include; } findINCEXE *get_incexe(JCR *jcr) @@ -1755,13 +1764,11 @@ static void add_fileset(JCR *jcr, const char *item) break; case 'F': /* file = */ /* File item to include or exclude list */ - state = state_include; - add_file_to_fileset(jcr, item, true); + state = add_file_to_fileset(jcr, item, true); break; case 'P': /* plugin */ /* Plugin item to include list */ - state = state_include; - add_file_to_fileset(jcr, item, false); + state = add_file_to_fileset(jcr, item, false); break; case 'R': /* regex */ state = add_regex_to_fileset(jcr, item, subcode); diff --git a/bacula/src/filed/protos.h b/bacula/src/filed/protos.h index 384893321..b9c643ba4 100644 --- a/bacula/src/filed/protos.h +++ b/bacula/src/filed/protos.h @@ -99,7 +99,7 @@ findINCEXE *new_preinclude(JCR *jcr); findINCEXE *get_incexe(JCR *jcr); void set_incexe(JCR *jcr, findINCEXE *incexe); void new_options(JCR *jcr, findINCEXE *incexe); -void add_file_to_fileset(JCR *jcr, const char *fname, bool is_file); +int add_file_to_fileset(JCR *jcr, const char *fname, bool is_file); int add_options_to_fileset(JCR *jcr, const char *item); int add_wild_to_fileset(JCR *jcr, const char *item, int type); int add_regex_to_fileset(JCR *jcr, const char *item, int type); diff --git a/bacula/src/jcr.h b/bacula/src/jcr.h index b40ad7be8..53d50569c 100644 --- a/bacula/src/jcr.h +++ b/bacula/src/jcr.h @@ -274,6 +274,8 @@ public: pthread_mutex_t msg_queue_mutex; /* message queue mutex */ bool dequeuing_msgs; /* Set when dequeuing messages */ alist job_end_push; /* Job end pushed calls */ + alist *allowed_script_dirs; /* Daemon-specific Allowed directory list to run + scripts/programs from */ POOLMEM *VolumeName; /* Volume name desired -- pool_memory */ POOLMEM *errmsg; /* edited error message */ char Job[MAX_NAME_LENGTH]; /* Unique name of this Job */ diff --git a/bacula/src/lib/bpipe.c b/bacula/src/lib/bpipe.c index e1833b32e..80de02714 100644 --- a/bacula/src/lib/bpipe.c +++ b/bacula/src/lib/bpipe.c @@ -70,6 +70,24 @@ void build_sh_argc_argv(char *cmd, int *bargc, char *bargv[], int max_arg) *bargc = 3; } +int forbidden_chars[] = { + '$', '!', ';', '\\', '&', '|', '<', '>', '`', '(', ')' +}; + +static bool check_for_forbidden_chars(const char *str) +{ + bool ret = true; + int size = sizeof(forbidden_chars) / sizeof(int); + for (int i=0; iallowed_script_dirs) { + /* JCR has some allowed script directories set, so we need to check if command matches it */ + char *dir, *p; + foreach_alist(dir, jcr->allowed_script_dirs) { + if ((p = b_path_match(prog, dir)) == prog) { + /* Path is ok, now check if program contains forbidden characters */ + //TODO too many ifs, refactor that + if (!check_for_forbidden_chars(prog)) { + errno = berr_not_allowed_char; + allowed = false; + break; + } else { + allowed = true; + } + break; + } + } + + if (!allowed && errno != berr_not_allowed_char) { + errno = berr_not_allowed_path; + } + + } else { + /* Nothing specified, so we should allow to run scripts from every dir provided */ + allowed = true; + } + + /* Check if script/program can be executed */ + if (!allowed) { + return NULL; + } + bpipe = (BPIPE *)malloc(sizeof(BPIPE)); memset(bpipe, 0, sizeof(BPIPE)); if (strchr(mode,'r')) mode_map|=MODE_READ; -- 2.47.3