Skip to content

Commit 474e82d

Browse files
Mab879cursoragent
andcommitted
xinetd probe: bound paths and strans keys; export oscap_path_join
Use snprintf and length checks for stack buffers; build includedir paths with oscap_path_join. Mark oscap_path_join OSCAP_API for embedded tests. Add regression test for oversized name+protocol key. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1e7263a commit 474e82d

4 files changed

Lines changed: 225 additions & 45 deletions

File tree

src/OVAL/probes/unix/xinetd_probe.c

Lines changed: 188 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include <fnmatch.h>
5252
#include <bfind.h>
5353
#include <common/debug_priv.h>
54+
#include <common/public/oscap_helpers.h>
5455
#include <netdb.h>
5556

5657
#if defined(OS_FREEBSD)
@@ -185,6 +186,83 @@ typedef struct {
185186
unsigned int depth; /**< include depth */
186187
} xiconf_file_t;
187188

189+
static bool xiconf_path_has_prefix(const char *path, const char *prefix)
190+
{
191+
size_t plen;
192+
193+
if (path == NULL || prefix == NULL)
194+
return false;
195+
196+
plen = strlen(prefix);
197+
if (plen == 0)
198+
return false;
199+
200+
if (strncmp(path, prefix, plen) != 0)
201+
return false;
202+
203+
return (path[plen] == '\0' || path[plen] == PATH_SEPARATOR);
204+
}
205+
206+
static char *xiconf_make_absolute_include_path(const xiconf_file_t *xifile, const char *inclarg)
207+
{
208+
char *cpath_copy = NULL;
209+
char *base_dir = NULL;
210+
char *abs_path = NULL;
211+
212+
if (inclarg == NULL || *inclarg == '\0')
213+
return NULL;
214+
215+
if (inclarg[0] == PATH_SEPARATOR)
216+
return strdup(inclarg);
217+
218+
if (xifile == NULL || xifile->cpath == NULL || *xifile->cpath == '\0')
219+
return strdup(inclarg);
220+
221+
cpath_copy = strdup(xifile->cpath);
222+
if (cpath_copy == NULL)
223+
return NULL;
224+
225+
base_dir = oscap_dirname(cpath_copy);
226+
if (base_dir == NULL) {
227+
free(cpath_copy);
228+
return strdup(inclarg);
229+
}
230+
231+
abs_path = oscap_path_join(base_dir, inclarg);
232+
free(base_dir);
233+
free(cpath_copy);
234+
235+
return abs_path;
236+
}
237+
238+
static bool xiconf_validate_regular_file_path(const char *path)
239+
{
240+
struct stat st;
241+
242+
if (path == NULL)
243+
return false;
244+
if (stat(path, &st) != 0)
245+
return false;
246+
if (!S_ISREG(st.st_mode))
247+
return false;
248+
249+
return true;
250+
}
251+
252+
static bool xiconf_validate_dir_path(const char *path)
253+
{
254+
struct stat st;
255+
256+
if (path == NULL)
257+
return false;
258+
if (stat(path, &st) != 0)
259+
return false;
260+
if (!S_ISDIR(st.st_mode))
261+
return false;
262+
263+
return true;
264+
}
265+
188266
#define XICONF_FILE_MMAPED 0x00000001 /**< try to mmap the file */
189267
#define XICONF_FILE_PERSIST 0x00000002 /**< keep the file open/mmaped */
190268
#define XICONF_FILE_DEAD 0x00000004 /**< this item can be skipped/deleted/reused for a different file */
@@ -705,10 +783,6 @@ xiconf_t *xiconf_parse(const char *path, unsigned int max_depth)
705783
#define XICONF_INCTYPE_DIR 1
706784
int inctype = -1;
707785
char *inclarg;
708-
char pathbuf[PATH_MAX+1];
709-
size_t incllen;
710-
711-
incllen = strlen (buffer + bufidx);
712786

713787
if (strncmp("nclude", buffer + bufidx + 1, 6) != 0)
714788
break;
@@ -729,7 +803,7 @@ xiconf_t *xiconf_parse(const char *path, unsigned int max_depth)
729803
/*
730804
* Get the include(dir) argument
731805
*/
732-
bufidx += strlen("includedir");
806+
bufidx += (inctype == XICONF_INCTYPE_DIR) ? (sizeof("includedir") - 1) : (sizeof("include") - 1);
733807
while(isspace(buffer[bufidx])) ++bufidx;
734808
inclarg = buffer + bufidx + 1;
735809
buffer[l_size] = '\0';
@@ -746,42 +820,77 @@ xiconf_t *xiconf_parse(const char *path, unsigned int max_depth)
746820

747821
switch (inctype) {
748822
case XICONF_INCTYPE_FILE:
749-
strncpy (pathbuf, inclarg, sizeof(pathbuf)-1);
823+
{
824+
char resolved_path[PATH_MAX + 1];
825+
char *abs_path = NULL;
826+
char *canon_path = NULL;
827+
828+
abs_path = xiconf_make_absolute_include_path(xifile, inclarg);
829+
if (abs_path == NULL) {
830+
dW("includefile: failed to resolve path");
831+
tmpbuf_free(buffer);
832+
continue;
833+
}
750834

751-
dD("includefile: %s", pathbuf);
835+
canon_path = oscap_realpath(abs_path, resolved_path);
836+
free(abs_path);
837+
abs_path = NULL;
752838

753-
if (xiconf_add_cfile (xiconf, pathbuf, xifile->depth + 1) != 0) {
839+
if (canon_path == NULL) {
840+
dW("includefile: invalid path: %s", inclarg);
841+
tmpbuf_free(buffer);
842+
continue;
843+
}
844+
845+
if (!xiconf_validate_regular_file_path(resolved_path)) {
846+
dW("includefile: not a regular file: %s", resolved_path);
847+
tmpbuf_free(buffer);
848+
continue;
849+
}
850+
851+
dD("includefile: %s", resolved_path);
852+
853+
if (xiconf_add_cfile (xiconf, resolved_path, xifile->depth + 1) != 0) {
754854
tmpbuf_free(buffer);
755855
continue;
756856
}
757857
else
758858
break;
859+
}
759860
case XICONF_INCTYPE_DIR:
760861
{
761862
DIR *dirfp;
762863
struct dirent *dent = NULL;
864+
char resolved_dir[PATH_MAX + 1];
865+
char *abs_dir = NULL;
866+
char *canon_dir = NULL;
763867

764-
dD("includedir open: %s", inclarg);
765-
dirfp = opendir (inclarg);
868+
abs_dir = xiconf_make_absolute_include_path(xifile, inclarg);
869+
if (abs_dir == NULL) {
870+
dW("includedir: failed to resolve path");
871+
break;
872+
}
766873

767-
if (dirfp == NULL) {
768-
dW("Can't open includedir: %s; %d, %s.", inclarg, errno, strerror (errno));
874+
canon_dir = oscap_realpath(abs_dir, resolved_dir);
875+
free(abs_dir);
876+
abs_dir = NULL;
877+
878+
if (canon_dir == NULL) {
879+
dW("includedir: invalid path: %s", inclarg);
769880
break;
770881
}
771882

772-
strcpy (pathbuf, inclarg);
773-
incllen = strlen(inclarg);
774-
775-
if (pathbuf[incllen - 1] != PATH_SEPARATOR) {
776-
if (incllen < PATH_MAX) {
777-
pathbuf[incllen++] = '/';
778-
pathbuf[incllen ] = '\0';
779-
} else {
780-
dE("Length of the includedir argument is out of range: len=%zu, max=%zu",
781-
incllen, PATH_MAX);
782-
closedir(dirfp);
783-
break;
784-
}
883+
if (!xiconf_validate_dir_path(resolved_dir)) {
884+
dW("includedir: not a directory: %s", resolved_dir);
885+
break;
886+
}
887+
888+
dD("includedir open: %s", resolved_dir);
889+
dirfp = opendir (resolved_dir);
890+
891+
if (dirfp == NULL) {
892+
dW("Can't open includedir: %s; %d, %s.", resolved_dir, errno, strerror (errno));
893+
break;
785894
}
786895

787896
for (;;) {
@@ -800,13 +909,50 @@ xiconf_t *xiconf_parse(const char *path, unsigned int max_depth)
800909
continue;
801910
}
802911

803-
strcpy(pathbuf + incllen, dent->d_name);
912+
if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
913+
dD("Skipping: %s", dent->d_name);
914+
continue;
915+
}
916+
917+
if (strchr(dent->d_name, PATH_SEPARATOR) != NULL) {
918+
dW("Skipping suspicious includedir entry: %s", dent->d_name);
919+
continue;
920+
}
921+
922+
char *entry_path = oscap_path_join(resolved_dir, dent->d_name);
923+
char resolved_entry[PATH_MAX + 1];
924+
char *canon_entry = NULL;
925+
926+
if (entry_path == NULL) {
927+
dW("Can't build path for includedir entry: %s/%s",
928+
resolved_dir, dent->d_name);
929+
continue;
930+
}
931+
932+
canon_entry = oscap_realpath(entry_path, resolved_entry);
933+
free(entry_path);
934+
entry_path = NULL;
935+
936+
if (canon_entry == NULL) {
937+
dW("Skipping includedir entry with invalid path: %s", dent->d_name);
938+
continue;
939+
}
940+
941+
if (!xiconf_path_has_prefix(resolved_entry, resolved_dir)) {
942+
dW("Skipping includedir entry escaping base dir: %s", resolved_entry);
943+
continue;
944+
}
945+
946+
if (!xiconf_validate_regular_file_path(resolved_entry)) {
947+
dD("Skipping non-regular includedir entry: %s", resolved_entry);
948+
continue;
949+
}
804950

805-
if (xiconf_add_cfile (xiconf, pathbuf, xifile->depth + 1) != 0)
951+
if (xiconf_add_cfile (xiconf, resolved_entry, xifile->depth + 1) != 0)
806952
continue;
807953
}
808954

809-
dD("includedir close: %s", inclarg);
955+
dD("includedir close: %s", resolved_dir);
810956
closedir(dirfp);
811957
break;
812958
}}
@@ -959,7 +1105,7 @@ int xiconf_parse_section(xiconf_t *xiconf, xiconf_file_t *xifile, int type, char
9591105

9601106
goto finish_section;
9611107
default:
962-
op = key + strlen(key) + 1;
1108+
op = key + keyidx + 1;
9631109

9641110
while(isspace(*op)) ++op;
9651111

@@ -1139,10 +1285,19 @@ int xiconf_parse_section(xiconf_t *xiconf, xiconf_file_t *xifile, int type, char
11391285
* Add entry to the ttree for (name, protocol) -> (id) translation
11401286
* (in case it's not already there)
11411287
*/
1142-
st = NULL;
1143-
strcpy(st_key, scur->name);
1144-
strcat(st_key, scur->protocol);
1288+
{
1289+
const char *st_name = scur->name != NULL ? scur->name : "";
1290+
const char *st_prot = scur->protocol != NULL ? scur->protocol : "";
11451291

1292+
if (strlen(st_name) + strlen(st_prot) > XICFG_STRANS_MAXKEYLEN) {
1293+
dE("service name+protocol too long for strans key (max %u)",
1294+
XICFG_STRANS_MAXKEYLEN);
1295+
return (-1);
1296+
}
1297+
snprintf(st_key, sizeof(st_key), "%s%s", st_name, st_prot);
1298+
}
1299+
1300+
st = NULL;
11461301
rbt_str_get(xiconf->ttree, st_key, (void *)&st);
11471302

11481303
if (st == NULL) {
@@ -1222,8 +1377,7 @@ xiconf_strans_t *xiconf_getservice(xiconf_t *xiconf, char *name, char *prot)
12221377
if (strlen(name) + strlen(prot) > XICFG_STRANS_MAXKEYLEN)
12231378
return (NULL);
12241379

1225-
strcpy(strans_key, name);
1226-
strcat(strans_key, prot);
1380+
snprintf(strans_key, sizeof(strans_key), "%s%s", name, prot);
12271381

12281382
rbt_str_get(xiconf->ttree, strans_key, (void *)&strans);
12291383

src/common/public/oscap.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ OSCAP_API const char * oscap_path_to_schemas(void);
136136
*/
137137
OSCAP_API const char * oscap_path_to_cpe(void);
138138

139+
/**
140+
* Join 2 paths in an intelligent way.
141+
* Both paths are allowed to be NULL.
142+
* Caller is responsible for freeing the returned pointer.
143+
* @param path1 first path
144+
* @param path2 second path
145+
* @return Join of path1 and path2. The first path is separated by the second
146+
* path by exactly 1 slash separator.
147+
*/
148+
OSCAP_API char *oscap_path_join(const char *path1, const char *path2);
149+
139150
/************************************************************/
140151
/** @} validation group end */
141152

src/common/util.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -396,17 +396,6 @@ char *oscap_vsprintf(const char *fmt, va_list ap);
396396
*/
397397
char *oscap_generate_random_string(size_t len, char *charset);
398398

399-
/**
400-
* Join 2 paths in an intelligent way.
401-
* Both paths are allowed to be NULL.
402-
* Caller is responsible for freeing the returned pointer.
403-
* @param path1 first path
404-
* @param path2 second path
405-
* @return Join of path1 and path2. The first path is separated by the second
406-
* path by exactly 1 slash separator.
407-
*/
408-
char *oscap_path_join(const char *path1, const char *path2);
409-
410399
/// In a list of key-value pairs (odd indicies are keys, even values), find a value for given key
411400
const char *oscap_strlist_find_value(char ** const kvalues, const char *key);
412401
/// Right trim @a ch characters (modifies its first argument!)

tests/probes/xinetd/test_xinetd_probe.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,31 @@ function test_probe_xinetd_duplicates {
8080
return 1
8181
}
8282

83+
# Name + protocol must fit XICFG_STRANS_MAXKEYLEN (parser rejects oversize keys).
84+
function test_probe_xinetd_strans_key_too_long {
85+
local ret_val=0
86+
local tmpconf
87+
local longname
88+
89+
tmpconf=$(mktemp)
90+
longname=$(printf '%260s' | tr ' ' 'a')
91+
{
92+
printf 'service %s\n' "$longname"
93+
printf '{\n'
94+
printf 'protocol = tcp\n'
95+
printf 'socket_type = stream\n'
96+
printf '}\n'
97+
} >"$tmpconf"
98+
99+
./test_probe_xinetd "$tmpconf" ignored tcp
100+
if [ $? -ne 2 ]; then
101+
ret_val=1
102+
fi
103+
104+
rm -f "$tmpconf"
105+
return $ret_val
106+
}
107+
83108
# Testing.
84109

85110
test_init
@@ -88,6 +113,7 @@ if [ -z ${CUSTOM_OSCAP+x} ] ; then
88113
test_run "test_probe_xinetd_parser" test_probe_xinetd_parser
89114
test_run "xinetd parser regression test: string list" test_probe_xinetd_regression_stringlist
90115
test_run "test_probe_xinetd_duplicates" test_probe_xinetd_duplicates
116+
test_run "test_probe_xinetd_strans_key_too_long" test_probe_xinetd_strans_key_too_long
91117
fi
92118

93119
test_exit

0 commit comments

Comments
 (0)