Skip to content

Commit aaf314a

Browse files
committed
Packages promiser is now escaped when using shell commands
I also refactored the code to use a dynamic buffer when assembling the commands. Estimating the buffer size needed is error prone and can quickly lead to buffer overflows. Ticket: ENT-13535 Changelog: Title Signed-off-by: Lars Erik Wik <[email protected]>
1 parent ef1f52a commit aaf314a

File tree

2 files changed

+142
-66
lines changed

2 files changed

+142
-66
lines changed

cf-agent/verify_packages.c

Lines changed: 50 additions & 66 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
@@ -2578,35 +2579,9 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
25782579
continue;
25792580
}
25802581

2581-
size_t estimated_size = 0;
2582-
2583-
for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
2584-
{
2585-
size_t size = strlen(pi->name) + strlen(" ");
2586-
2587-
switch (pm->policy)
2588-
{
2589-
case PACKAGE_ACTION_POLICY_INDIVIDUAL:
2590-
2591-
if (size > estimated_size)
2592-
{
2593-
estimated_size = size + CF_MAXVARSIZE;
2594-
}
2595-
break;
2596-
2597-
case PACKAGE_ACTION_POLICY_BULK:
2598-
2599-
estimated_size += size + CF_MAXVARSIZE;
2600-
break;
2601-
2602-
default:
2603-
break;
2604-
}
2605-
}
2606-
26072582
const Promise *const pp = pm->pack_list->pp;
26082583
Attributes a = GetPackageAttributes(ctx, pp);
2609-
char *command_string = NULL;
2584+
Buffer *command_buffer = BufferNew();
26102585

26112586
switch (action)
26122587
{
@@ -2617,14 +2592,12 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
26172592
if (a.packages.package_add_command == NULL)
26182593
{
26192594
ProgrammingError("Package add command undefined");
2595+
BufferDestroy(command_buffer);
26202596
return false;
26212597
}
26222598

26232599
Log(LOG_LEVEL_INFO, "Installing %-.39s...", pp->promiser);
2624-
2625-
estimated_size += strlen(a.packages.package_add_command) + 2;
2626-
command_string = xmalloc(estimated_size);
2627-
strcpy(command_string, a.packages.package_add_command);
2600+
BufferAppendString(command_buffer, a.packages.package_add_command);
26282601
break;
26292602

26302603
case PACKAGE_ACTION_DELETE:
@@ -2634,14 +2607,12 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
26342607
if (a.packages.package_delete_command == NULL)
26352608
{
26362609
ProgrammingError("Package delete command undefined");
2610+
BufferDestroy(command_buffer);
26372611
return false;
26382612
}
26392613

26402614
Log(LOG_LEVEL_INFO, "Deleting %-.39s...", pp->promiser);
2641-
2642-
estimated_size += strlen(a.packages.package_delete_command) + 2;
2643-
command_string = xmalloc(estimated_size);
2644-
strcpy(command_string, a.packages.package_delete_command);
2615+
BufferAppendString(command_buffer, a.packages.package_delete_command);
26452616
break;
26462617

26472618
case PACKAGE_ACTION_UPDATE:
@@ -2651,14 +2622,12 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
26512622
if (a.packages.package_update_command == NULL)
26522623
{
26532624
ProgrammingError("Package update command undefined");
2625+
BufferDestroy(command_buffer);
26542626
return false;
26552627
}
26562628

26572629
Log(LOG_LEVEL_INFO, "Updating %-.39s...", pp->promiser);
2658-
2659-
estimated_size += strlen(a.packages.package_update_command) + 2;
2660-
command_string = xcalloc(1, estimated_size);
2661-
strcpy(command_string, a.packages.package_update_command);
2630+
BufferAppendString(command_buffer, a.packages.package_update_command);
26622631
break;
26632632

26642633
case PACKAGE_ACTION_VERIFY:
@@ -2668,33 +2637,32 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
26682637
if (a.packages.package_verify_command == NULL)
26692638
{
26702639
ProgrammingError("Package verify command undefined");
2640+
BufferDestroy(command_buffer);
26712641
return false;
26722642
}
26732643

2674-
estimated_size += strlen(a.packages.package_verify_command) + 2;
2675-
command_string = xmalloc(estimated_size);
2676-
strcpy(command_string, a.packages.package_verify_command);
2644+
BufferAppendString(command_buffer, a.packages.package_verify_command);
26772645

26782646
verify = true;
26792647
break;
26802648

26812649
default:
26822650
ProgrammingError("Unknown action attempted");
2651+
BufferDestroy(command_buffer);
26832652
return false;
26842653
}
26852654

26862655
/* if the command ends with $ then we assume the package manager does not accept package names */
2687-
2688-
if (*(command_string + strlen(command_string) - 1) == '$')
2656+
if (BufferData(command_buffer)[BufferSize(command_buffer) - 1] == '$')
26892657
{
2690-
*(command_string + strlen(command_string) - 1) = '\0';
2658+
BufferTrimToMaxLength(command_buffer, BufferSize(command_buffer) - 1);
26912659
Log(LOG_LEVEL_VERBOSE, "Command does not allow arguments");
26922660
PromiseResult result = PROMISE_RESULT_NOOP;
26932661

26942662
EvalContextStackPushPromiseFrame(ctx, pp);
26952663
if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
26962664
{
2697-
if (ExecPackageCommand(ctx, command_string, verify, true, &a, pp, &result))
2665+
if (ExecPackageCommand(ctx, BufferData(command_buffer), verify, true, &a, pp, &result))
26982666
{
26992667
Log(LOG_LEVEL_VERBOSE, "Package schedule execution ok (outcome cannot be promised by cf-agent)");
27002668
}
@@ -2711,9 +2679,9 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27112679
}
27122680
else
27132681
{
2714-
strcat(command_string, " ");
2682+
BufferAppendChar(command_buffer, ' ');
27152683

2716-
Log(LOG_LEVEL_VERBOSE, "Command prefix '%s'", command_string);
2684+
Log(LOG_LEVEL_VERBOSE, "Command prefix '%s'", BufferData(command_buffer));
27172685

27182686
if (pm->policy == PACKAGE_ACTION_POLICY_INDIVIDUAL)
27192687
{
@@ -2723,15 +2691,14 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27232691
const Promise *const ppi = pi->pp;
27242692
Attributes a = GetPackageAttributes(ctx, ppi);
27252693

2726-
const size_t command_len = strlen(command_string);
2727-
char *offset = command_string + command_len;
2694+
const size_t offset = BufferSize(command_buffer);
27282695

27292696
if ((a.packages.package_file_repositories) && ((action == PACKAGE_ACTION_ADD) || (action == PACKAGE_ACTION_UPDATE)))
27302697
{
27312698
const char *sp = PrefixLocalRepository(a.packages.package_file_repositories, pi->name);
27322699
if (sp != NULL)
27332700
{
2734-
strlcat(offset, sp, estimated_size - command_len);
2701+
BufferAppendString(command_buffer, sp);
27352702
}
27362703
else
27372704
{
@@ -2740,14 +2707,26 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27402707
}
27412708
else
27422709
{
2743-
strcat(offset, pi->name);
2710+
if (a.packages.package_commands_useshell)
2711+
{
2712+
/* Put single quotes around package name and escape
2713+
* any pre-existing single quotes to prevent shell
2714+
* injection. */
2715+
char *escaped = EscapeCharCopy(pi->name, '\'', '\\');
2716+
BufferAppendF(command_buffer, "'%s'", escaped);
2717+
free(escaped);
2718+
}
2719+
else
2720+
{
2721+
BufferAppendString(command_buffer, pi->name);
2722+
}
27442723
}
27452724

27462725
PromiseResult result = PROMISE_RESULT_NOOP;
27472726
EvalContextStackPushPromiseFrame(ctx, ppi);
27482727
if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
27492728
{
2750-
bool ok = ExecPackageCommand(ctx, command_string, verify, true, &a, ppi, &result);
2729+
bool ok = ExecPackageCommand(ctx, BufferData(command_buffer), verify, true, &a, ppi, &result);
27512730

27522731
if (StringEqual(pi->name, PACKAGE_IGNORED_CFE_INTERNAL))
27532732
{
@@ -2770,7 +2749,7 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27702749

27712750
EvalContextLogPromiseIterationOutcome(ctx, ppi, result);
27722751

2773-
*offset = '\0';
2752+
BufferTrimToMaxLength(command_buffer, offset);
27742753
}
27752754
}
27762755
else if (pm->policy == PACKAGE_ACTION_POLICY_BULK)
@@ -2779,17 +2758,14 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27792758
{
27802759
if (pi->name)
27812760
{
2782-
const size_t command_len = strlen(command_string);
2783-
char *offset = command_string + command_len;
2784-
27852761
if (a.packages.package_file_repositories &&
27862762
(action == PACKAGE_ACTION_ADD ||
27872763
action == PACKAGE_ACTION_UPDATE))
27882764
{
27892765
const char *sp = PrefixLocalRepository(a.packages.package_file_repositories, pi->name);
27902766
if (sp != NULL)
27912767
{
2792-
strlcpy(offset, sp, estimated_size - command_len);
2768+
BufferAppendString(command_buffer, sp);
27932769
}
27942770
else
27952771
{
@@ -2798,18 +2774,29 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
27982774
}
27992775
else
28002776
{
2801-
strcpy(offset, pi->name);
2777+
if (a.packages.package_commands_useshell)
2778+
{
2779+
/* Put single quotes around package name and escape
2780+
* any pre-existing single quotes to prevent shell
2781+
* injection. */
2782+
char *escaped = EscapeCharCopy(pi->name, '\'', '\\');
2783+
BufferAppendF(command_buffer, "'%s'", escaped);
2784+
free(escaped);
2785+
}
2786+
else
2787+
{
2788+
BufferAppendString(command_buffer, pi->name);
2789+
}
28022790
}
2803-
2804-
strcat(command_string, " ");
2791+
BufferAppendChar(command_buffer, ' ');
28052792
}
28062793
}
28072794

28082795
PromiseResult result = PROMISE_RESULT_NOOP;
28092796
EvalContextStackPushPromiseFrame(ctx, pp);
28102797
if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
28112798
{
2812-
bool ok = ExecPackageCommand(ctx, command_string, verify, true, &a, pp, &result);
2799+
bool ok = ExecPackageCommand(ctx, BufferData(command_buffer), verify, true, &a, pp, &result);
28132800

28142801
for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
28152802
{
@@ -2837,10 +2824,7 @@ static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, Pa
28372824
}
28382825
}
28392826

2840-
if (command_string)
2841-
{
2842-
free(command_string);
2843-
}
2827+
BufferDestroy(command_buffer);
28442828
}
28452829

28462830
/* We have performed some modification operation on packages, our cache is invalid */
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)