Skip to content

Commit e865642

Browse files
committed
Added packages promiser sanitation
Ticket: ENT-13535 Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>
1 parent 42b2f68 commit e865642

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

cf-agent/verify_packages.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
#include <csv_writer.h>
5555
#include <cf-agent-enterprise-stubs.h>
5656
#include <cf-windows-functions.h>
57+
#include <cf3.defs.h>
5758

5859
/* Called structure:
5960
@@ -104,6 +105,7 @@ typedef enum
104105
PACKAGE_PROMISE_TYPE_NEW_ERROR
105106
} PackagePromiseType;
106107

108+
static bool SanitizePackagePromiser(EvalContext *ctx, const Attributes *a, const Promise *pp);
107109
static bool PackageSanityCheck(EvalContext *ctx, const Attributes *a, const Promise *pp);
108110

109111
static bool VerifyInstalledPackages(EvalContext *ctx, PackageManager **alllists, const char *default_arch, const Attributes *a, const Promise *pp, PromiseResult *result);
@@ -186,6 +188,12 @@ PromiseResult VerifyPackagesPromise(EvalContext *ctx, const Promise *pp)
186188
PackagePromiseType package_promise_type =
187189
GetPackagePromiseVersion(&a.packages, &a.new_packages);
188190

191+
if (!SanitizePackagePromiser(ctx, &a, pp))
192+
{
193+
Log(LOG_LEVEL_VERBOSE, "Package promise %s failed sanitization check", pp->promiser);
194+
return PROMISE_RESULT_FAIL;
195+
}
196+
189197
switch (package_promise_type)
190198
{
191199
case PACKAGE_PROMISE_TYPE_NEW:
@@ -368,6 +376,51 @@ static PromiseResult HandleOldPackagePromiseType(EvalContext *ctx, const Promise
368376
return result;
369377
}
370378

379+
static bool SanitizePackagePromiser(EvalContext *ctx, const Attributes *a, const Promise *pp)
380+
{
381+
assert(a != NULL);
382+
assert(pp != NULL);
383+
384+
const char *promiser = pp->promiser;
385+
386+
/* Shell metacharacters that could be used for command injection
387+
*
388+
* Here's the rationale for each shell metacharacter in the list:
389+
* ; Command separator, allows running additional commands
390+
* | Pipe, can redirect output to another command
391+
* & Background execution or command chaining (`&&`)
392+
* ` Command substitution (backticks execute enclosed command)
393+
* $ Variable expansion and command substitution (`$(...)`)
394+
* ( Subshell execution, command grouping
395+
* < Input/output redirection, can overwrite files
396+
* { Brace expansion, command grouping
397+
* [ Character class matching
398+
* * Wildcard, matches any characters
399+
* ? Wildcard, matches single character
400+
* ~ Home directory expansion
401+
* \ Escape character, can break out of quoting
402+
* ' Quotes, can break parsing if unbalanced
403+
* ! History expansion in some shells
404+
* # Comment character, can truncate commands
405+
* \n \r Newlines can inject additional commands
406+
*/
407+
const char *shell_metacharacters = ";|&`$(){}[]<>!#*?~\\'\"\n\r";
408+
409+
if (a->packages.package_commands_useshell)
410+
{
411+
const char *bad_char = strpbrk(promiser, shell_metacharacters);
412+
if (bad_char != NULL)
413+
{
414+
cfPS_HELPER_2ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
415+
"Package promiser '%s' contains shell metacharacter '%c' which could allow command injection",
416+
promiser, *bad_char);
417+
return false;
418+
}
419+
}
420+
421+
return true;
422+
}
423+
371424
/**
372425
@brief Pre-check of promise contents
373426
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#######################################################
2+
#
3+
# See ticket ENT-13536 for info
4+
#
5+
#######################################################
6+
7+
body common control
8+
{
9+
inputs => { "../default.cf.sub" };
10+
bundlesequence => { "G", "g", default("$(this.promise_filename)") };
11+
version => "1.0";
12+
cache_system_functions => "false";
13+
}
14+
15+
body package_method mock
16+
{
17+
package_changes => "individual";
18+
package_list_command => "$(g.pm) --list-installed";
19+
20+
package_list_name_regex => "^[^:]*";
21+
package_list_version_regex => ":(?<=:).*(?=:)";
22+
package_list_arch_regex => "[^:]\w+$";
23+
package_installed_regex => "^[^:]*";
24+
25+
package_add_command => "$(g.pm) --add ";
26+
package_update_command => "$(g.pm) --update ";
27+
package_delete_command => "$(g.pm) --delete ";
28+
package_verify_command => "$(g.pm) --verify ";
29+
}
30+
31+
bundle common g
32+
{
33+
classes:
34+
"mpm_declared" not => strcmp(getenv("MOCK_PACKAGE_MANAGER", "65535"), "");
35+
36+
vars:
37+
mpm_declared::
38+
"pm" string => getenv("MOCK_PACKAGE_MANAGER", "65535");
39+
40+
!mpm_declared::
41+
"pm" string => "$(G.mock_package_manager)";
42+
}
43+
44+
#######################################################
45+
46+
bundle agent init
47+
{
48+
commands:
49+
"$(g.pm) --clear-installed";
50+
"$(g.pm) --clear-available";
51+
"$(g.pm) --populate-available imagisoft:1.0i:teapot";
52+
53+
files:
54+
"$(G.testfile)"
55+
delete => tidy;
56+
}
57+
58+
#######################################################
59+
60+
bundle agent test
61+
{
62+
meta:
63+
"test_skip_unsupported" string => "windows",
64+
meta => { "ENT-10215" };
65+
66+
vars:
67+
"name" string => "imagisoft";
68+
"version" string => "1.0i";
69+
"arch" string => "teapot";
70+
71+
packages:
72+
"imagisoft; echo 'Hello CFEngine' > '$(G.testfile)';"
73+
package_policy => "add",
74+
package_method => mock;
75+
}
76+
77+
#######################################################
78+
79+
bundle agent check
80+
{
81+
reports:
82+
"$(this.promise_filename) $(with)"
83+
with => ifelse(fileexists("$(G.testfile)"), "FAIL", "Pass");
84+
}
85+
86+
#######################################################
87+
88+
bundle agent clean
89+
{
90+
methods:
91+
"init";
92+
}

0 commit comments

Comments
 (0)