Skip to content

Commit 0fdb8da

Browse files
committed
out_file: sanitize incoming Tag to prevent path traversal
Adds a dedicated sanitize_tag_name helper that walks each tag component, strips traversal tokens, replaces unsafe characters, and falls back to _ when the cleaned result would otherwise be empty—keeping derived filenames safely under the configured base directory. Signed-off-by: Eduardo Silva <[email protected]>
1 parent b3f9092 commit 0fdb8da

File tree

1 file changed

+111
-6
lines changed

1 file changed

+111
-6
lines changed

plugins/out_file/file.c

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
#include <fluent-bit/flb_log_event_decoder.h>
2727
#include <msgpack.h>
2828

29+
#include <ctype.h>
30+
2931
#include <stdio.h>
32+
#include <string.h>
3033
#include <sys/types.h>
3134
#include <sys/stat.h>
3235
#include <fcntl.h>
@@ -82,6 +85,96 @@ static char *check_delimiter(const char *str)
8285
return NULL;
8386
}
8487

88+
/*
89+
* Convert the user-controlled tag into a safe, relative path. Each component is
90+
* cleaned so only filesystem-friendly characters remain, ".." segments are
91+
* collapsed into a single underscore and leading separators are dropped to
92+
* guarantee the result stays within the configured base path.
93+
*/
94+
static int sanitize_tag_name(const char *tag, char *buf, size_t size)
95+
{
96+
size_t i;
97+
size_t out_len = 0;
98+
size_t component_len;
99+
char sanitized_char;
100+
const char *p;
101+
const char *component_start;
102+
103+
if (tag == NULL || buf == NULL || size < 2) {
104+
return -1;
105+
}
106+
107+
p = tag;
108+
109+
while (*p != '\0') {
110+
component_len = 0;
111+
component_start = NULL;
112+
113+
/* Skip consecutive separators so we never generate empty components */
114+
while (*p == '/' || *p == '\\') {
115+
p++;
116+
}
117+
118+
if (*p == '\0') {
119+
break;
120+
}
121+
122+
component_start = p;
123+
124+
while (*p != '\0' && *p != '/' && *p != '\\') {
125+
component_len++;
126+
p++;
127+
}
128+
129+
if (component_len == 0) {
130+
continue;
131+
}
132+
133+
if (out_len > 0) {
134+
if (out_len >= size - 1) {
135+
break;
136+
}
137+
138+
buf[out_len++] = FLB_PATH_SEPARATOR[0];
139+
}
140+
141+
if ((component_len == 1 && component_start[0] == '.') ||
142+
(component_len == 2 && component_start[0] == '.' && component_start[1] == '.')) {
143+
if (out_len >= size - 1) {
144+
break;
145+
}
146+
147+
buf[out_len++] = '_';
148+
continue;
149+
}
150+
151+
for (i = 0; i < component_len; i++) {
152+
sanitized_char = component_start[i];
153+
154+
if (!isalnum((unsigned char) sanitized_char) && sanitized_char != '-' &&
155+
sanitized_char != '_' && sanitized_char != '.') {
156+
sanitized_char = '_';
157+
}
158+
159+
if (out_len >= size - 1) {
160+
break;
161+
}
162+
163+
buf[out_len++] = sanitized_char;
164+
}
165+
}
166+
167+
if (out_len == 0) {
168+
buf[0] = '_';
169+
buf[1] = '\0';
170+
return 0;
171+
}
172+
173+
buf[out_len] = '\0';
174+
175+
return 0;
176+
}
177+
85178

86179
static int cb_file_init(struct flb_output_instance *ins,
87180
struct flb_config *config,
@@ -502,7 +595,8 @@ static void cb_file_flush(struct flb_event_chunk *event_chunk,
502595
size_t last_off = 0;
503596
size_t alloc_size = 0;
504597
size_t total;
505-
char out_file[PATH_MAX];
598+
char out_file[PATH_MAX * 2];
599+
char sanitized_tag[PATH_MAX];
506600
char *buf;
507601
long file_pos;
508602
struct flb_file_conf *ctx = out_context;
@@ -513,22 +607,33 @@ static void cb_file_flush(struct flb_event_chunk *event_chunk,
513607
(void) config;
514608

515609
/* Set the right output file */
610+
if (ctx->out_file == NULL) {
611+
ret = sanitize_tag_name(event_chunk->tag,
612+
sanitized_tag,
613+
sizeof(sanitized_tag));
614+
615+
if (ret != 0) {
616+
flb_plg_error(ctx->ins, "failed to sanitize tag for output file");
617+
FLB_OUTPUT_RETURN(FLB_ERROR);
618+
}
619+
}
620+
516621
if (ctx->out_path) {
517622
if (ctx->out_file) {
518-
snprintf(out_file, PATH_MAX - 1, "%s" FLB_PATH_SEPARATOR "%s",
623+
snprintf(out_file, sizeof(out_file) , "%s" FLB_PATH_SEPARATOR "%s",
519624
ctx->out_path, ctx->out_file);
520625
}
521626
else {
522-
snprintf(out_file, PATH_MAX - 1, "%s" FLB_PATH_SEPARATOR "%s",
523-
ctx->out_path, event_chunk->tag);
627+
snprintf(out_file, sizeof(out_file), "%s" FLB_PATH_SEPARATOR "%s",
628+
ctx->out_path, sanitized_tag);
524629
}
525630
}
526631
else {
527632
if (ctx->out_file) {
528-
snprintf(out_file, PATH_MAX - 1, "%s", ctx->out_file);
633+
snprintf(out_file, PATH_MAX, "%s", ctx->out_file);
529634
}
530635
else {
531-
snprintf(out_file, PATH_MAX - 1, "%s", event_chunk->tag);
636+
snprintf(out_file, PATH_MAX, "%s", sanitized_tag);
532637
}
533638
}
534639

0 commit comments

Comments
 (0)