@cryptotaxi247 / netdata-1 / commits / 5204ee24a

properly sanitize prometheus names and values (#18884)

* properly sanitize prometheus names and values * fix comment * label names cannot have spaces * label values have spaces * send content-type to text based protocols * proper fix for not allowing spaces in label names

Costa Tsaousis committed Oct 28, 2024 at 12:36 UTC 5204ee24a90bb27f71f2af4cfdf574f43eb13e55
7 files changed +98 -90
src/database/rrdlabels.c
+15
@@ -1158,6 +1158,9 @@ static int rrdlabels_unittest_add_pairs() {
1158 // test newlines
1159 errors += rrdlabels_unittest_add_a_pair(" tag = \t value \r\n", "tag", "value");
1160
1161 + // test spaces in names
1162 + errors += rrdlabels_unittest_add_a_pair(" t a g = value", "t_a_g", "value");
1163 +
1164 // test : in values
1165 errors += rrdlabels_unittest_add_a_pair("tag=:value", "tag", ":value");
1166 errors += rrdlabels_unittest_add_a_pair("tag::value", "tag", ":value");
@@ -1548,6 +1551,18 @@ int rrdlabels_unittest_sanitization() {
1551 // mixed multi-byte
1552 errors += rrdlabels_unittest_sanitize_value("Ű‱𩸽‱Ű", "Ű‱𩸽‱Ű");
1553
1554 + // invalid UTF8 No 1
1555 + const unsigned char invalid1[] = { 0xC3, 0x28, 'A', 'B', 0x0 };
1556 + errors += rrdlabels_unittest_sanitize_value((const char *)invalid1, "(AB");
1557 +
1558 + // invalid UTF8 No 2
1559 + const unsigned char invalid2[] = { 'A', 'B', 0xC3, 0x28, 'C', 'D', 0x0 };
1560 + errors += rrdlabels_unittest_sanitize_value((const char *)invalid2, "AB (CD");
1561 +
1562 + // invalid UTF8 No 3
1563 + const unsigned char invalid3[] = { 'A', 'B', 0xC3, 0x28, 0x0 };
1564 + errors += rrdlabels_unittest_sanitize_value((const char *)invalid3, "AB (");
1565 +
1566 return errors;
1567 }
1568
src/exporting/prometheus/prometheus.c
+19 -47
@@ -149,24 +149,11 @@ static inline time_t prometheus_server_last_access(const char *server, RRDHOST *
149 *
150 * @param d a destination string.
151 * @param s a source string.
152 - * @param usable the number of characters to copy.
152 + * @param size the number of characters to copy.
153 * @return Returns the length of the copied string.
154 */
155 -inline size_t prometheus_name_copy(char *d, const char *s, size_t usable)
156 -{
157 - size_t n;
158 -
159 - for (n = 0; *s && n < usable; d++, s++, n++) {
160 - register char c = *s;
161 -
162 - if (!isalnum(c))
163 - *d = '_';
164 - else
165 - *d = c;
166 - }
167 - *d = '\0';
168 -
169 - return n;
155 +inline void prometheus_name_copy(char *d, const char *s, size_t size) {
156 + prometheus_rrdlabels_sanitize_name(d, s, size);
157 }
158
159 /**
@@ -174,28 +161,13 @@ inline size_t prometheus_name_copy(char *d, const char *s, size_t usable)
161 *
162 * @param d a destination string.
163 * @param s a source string.
177 - * @param usable the number of characters to copy.
164 + * @param size the number of characters to copy.
165 * @return Returns the length of the copied string.
166 */
180 -inline size_t prometheus_label_copy(char *d, const char *s, size_t usable)
181 -{
182 - size_t n;
183 -
184 - // make sure we can escape one character without overflowing the buffer
185 - usable--;
186 -
187 - for (n = 0; *s && n < usable; d++, s++, n++) {
188 - register char c = *s;
189 -
190 - if (unlikely(c == '"' || c == '\\' || c == '\n')) {
191 - *d++ = '\\';
192 - n++;
193 - }
194 - *d = c;
195 - }
196 - *d = '\0';
197 -
198 - return n;
167 +inline void prometheus_label_copy(char *d, const char *s, size_t size) {
168 + // our label values are already compatible with prometheus label values
169 + // so, just copy them
170 + strncpyz(d, s, size - 1);
171 }
172
173 /**
@@ -299,8 +271,8 @@ static int format_prometheus_label_callback(const char *name, const char *value,
271 char k[PROMETHEUS_ELEMENT_MAX + 1];
272 char v[PROMETHEUS_ELEMENT_MAX + 1];
273
302 - prometheus_name_copy(k, name, PROMETHEUS_ELEMENT_MAX);
303 - prometheus_label_copy(v, value, PROMETHEUS_ELEMENT_MAX);
274 + prometheus_name_copy(k, name, sizeof(k));
275 + prometheus_label_copy(v, value, sizeof(v));
276
277 if (*k && *v) {
278 if (d->count > 0) buffer_strcat(d->instance->labels_buffer, ",");
@@ -341,8 +313,8 @@ static int format_prometheus_chart_label_callback(const char *name, const char *
313 char k[PROMETHEUS_ELEMENT_MAX + 1];
314 char v[PROMETHEUS_ELEMENT_MAX + 1];
315
344 - prometheus_name_copy(k, name, PROMETHEUS_ELEMENT_MAX);
345 - prometheus_label_copy(v, value, PROMETHEUS_ELEMENT_MAX);
316 + prometheus_name_copy(k, name, sizeof(k));
317 + prometheus_label_copy(v, value, sizeof(v));
318
319 if (*k && *v)
320 buffer_sprintf(wb, ",%s=\"%s\"", k, v);
@@ -630,9 +602,9 @@ static int prometheus_rrdset_to_json(RRDSET *st, void *data)
602
603 prometheus_label_copy(chart,
604 (output_options & PROMETHEUS_OUTPUT_NAMES && st->name) ?
633 - rrdset_name(st) : rrdset_id(st), PROMETHEUS_ELEMENT_MAX);
634 - prometheus_label_copy(family, rrdset_family(st), PROMETHEUS_ELEMENT_MAX);
635 - prometheus_name_copy(context, rrdset_context(st), PROMETHEUS_ELEMENT_MAX);
605 + rrdset_name(st) : rrdset_id(st), sizeof(chart));
606 + prometheus_label_copy(family, rrdset_family(st), sizeof(family));
607 + prometheus_name_copy(context, rrdset_context(st), sizeof(context));
608
609 int as_collected = (EXPORTING_OPTIONS_DATA_SOURCE(opts->exporting_options)
610 == EXPORTING_SOURCE_DATA_AS_COLLECTED);
@@ -708,7 +680,7 @@ static int prometheus_rrdset_to_json(RRDSET *st, void *data)
680 prometheus_label_copy(
681 dimension,
682 (output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
711 - PROMETHEUS_ELEMENT_MAX);
683 + sizeof(dimension));
684 }
685 else {
686 // the dimensions of the chart, do not have the same algorithm, multiplier or divisor
@@ -717,7 +689,7 @@ static int prometheus_rrdset_to_json(RRDSET *st, void *data)
689 prometheus_name_copy(
690 dimension,
691 (output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
720 - PROMETHEUS_ELEMENT_MAX);
692 + sizeof(dimension));
693 }
694 generate_as_collected_from_metric(wb, &p, homogeneous, prometheus_collector, st->rrdlabels);
695 }
@@ -738,7 +710,7 @@ static int prometheus_rrdset_to_json(RRDSET *st, void *data)
710 prometheus_label_copy(
711 dimension,
712 (output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
741 - PROMETHEUS_ELEMENT_MAX);
713 + sizeof(dimension));
714
715 if (opts->output_options & PROMETHEUS_OUTPUT_HELP_TYPE) {
716 generate_as_collected_prom_help(wb, prefix, context, units, suffix, st);
@@ -837,7 +809,7 @@ static void rrd_stats_api_v1_charts_allmetrics_prometheus(
809 SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT, true);
810
811 char hostname[PROMETHEUS_ELEMENT_MAX + 1];
840 - prometheus_label_copy(hostname, rrdhost_hostname(host), PROMETHEUS_ELEMENT_MAX);
812 + prometheus_label_copy(hostname, rrdhost_hostname(host), sizeof(hostname));
813
814 format_host_labels_prometheus(instance, host);
815
src/exporting/prometheus/prometheus.h
+2 -2
@@ -27,8 +27,8 @@ void rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
27 EXPORTING_OPTIONS exporting_options, PROMETHEUS_OUTPUT_OPTIONS output_options);
28
29 int can_send_rrdset(struct instance *instance, RRDSET *st, SIMPLE_PATTERN *filter);
30 -size_t prometheus_name_copy(char *d, const char *s, size_t usable);
31 -size_t prometheus_label_copy(char *d, const char *s, size_t usable);
30 +void prometheus_name_copy(char *d, const char *s, size_t size);
31 +void prometheus_label_copy(char *d, const char *s, size_t size);
32 char *prometheus_units_copy(char *d, const char *s, size_t usable, int showoldunits);
33
34 void format_host_labels_prometheus(struct instance *instance, RRDHOST *host);
src/exporting/prometheus/remote_write/remote_write.c
+12 -12
@@ -145,8 +145,8 @@ static int format_remote_write_label_callback(const char *name, const char *valu
145 char k[PROMETHEUS_ELEMENT_MAX + 1];
146 char v[PROMETHEUS_ELEMENT_MAX + 1];
147
148 - prometheus_name_copy(k, name, PROMETHEUS_ELEMENT_MAX);
149 - prometheus_label_copy(v, value, PROMETHEUS_ELEMENT_MAX);
148 + prometheus_name_copy(k, name, sizeof(k));
149 + prometheus_label_copy(v, value, sizeof(v));
150 add_label(d->write_request, k, v);
151 return 1;
152 }
@@ -169,7 +169,7 @@ int format_host_prometheus_remote_write(struct instance *instance, RRDHOST *host
169 prometheus_label_copy(
170 hostname,
171 (host == localhost) ? instance->config.hostname : rrdhost_hostname(host),
172 - PROMETHEUS_ELEMENT_MAX);
172 + sizeof(hostname));
173
174 add_host_info(
175 connector_specific_data->write_request,
@@ -198,9 +198,9 @@ int format_chart_prometheus_remote_write(struct instance *instance, RRDSET *st)
198 prometheus_label_copy(
199 chart,
200 (instance->config.options & EXPORTING_OPTION_SEND_NAMES && st->name) ? rrdset_name(st) : rrdset_id(st),
201 - PROMETHEUS_ELEMENT_MAX);
202 - prometheus_label_copy(family, rrdset_family(st), PROMETHEUS_ELEMENT_MAX);
203 - prometheus_name_copy(context, rrdset_context(st), PROMETHEUS_ELEMENT_MAX);
201 + sizeof(chart));
202 + prometheus_label_copy(family, rrdset_family(st), sizeof(family));
203 + prometheus_name_copy(context, rrdset_context(st), sizeof(context));
204
205 as_collected = (EXPORTING_OPTIONS_DATA_SOURCE(instance->config.options) == EXPORTING_SOURCE_DATA_AS_COLLECTED);
206 homogeneous = 1;
@@ -266,7 +266,7 @@ int format_dimension_prometheus_remote_write(struct instance *instance, RRDDIM *
266 prometheus_label_copy(
267 dimension,
268 (instance->config.options & EXPORTING_OPTION_SEND_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
269 - PROMETHEUS_ELEMENT_MAX);
269 + sizeof(dimension));
270 snprintf(name, PROMETHEUS_LABELS_MAX, "%s_%s%s", instance->config.prefix, context, suffix);
271
272 add_metric(
@@ -281,9 +281,9 @@ int format_dimension_prometheus_remote_write(struct instance *instance, RRDDIM *
281 prometheus_name_copy(
282 dimension,
283 (instance->config.options & EXPORTING_OPTION_SEND_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
284 - PROMETHEUS_ELEMENT_MAX);
284 + sizeof(dimension));
285 snprintf(
286 - name, PROMETHEUS_LABELS_MAX, "%s_%s_%s%s", instance->config.prefix, context, dimension,
286 + name, sizeof(name), "%s_%s_%s%s", instance->config.prefix, context, dimension,
287 suffix);
288
289 add_metric(
@@ -307,7 +307,7 @@ int format_dimension_prometheus_remote_write(struct instance *instance, RRDDIM *
307 prometheus_label_copy(
308 dimension,
309 (instance->config.options & EXPORTING_OPTION_SEND_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
310 - PROMETHEUS_ELEMENT_MAX);
310 + sizeof(dimension));
311 snprintf(
312 name, PROMETHEUS_LABELS_MAX, "%s_%s%s%s", instance->config.prefix, context, units, suffix);
313
@@ -338,8 +338,8 @@ static int format_variable_prometheus_remote_write_callback(const DICTIONARY_ITE
338 char name[PROMETHEUS_LABELS_MAX + 1];
339 char *suffix = "";
340
341 - prometheus_name_copy(context, rrdvar_name(rv), PROMETHEUS_ELEMENT_MAX);
342 - snprintf(name, PROMETHEUS_LABELS_MAX, "%s_%s%s", instance->config.prefix, context, suffix);
341 + prometheus_name_copy(context, rrdvar_name(rv), sizeof(context));
342 + snprintf(name, sizeof(name), "%s_%s%s", instance->config.prefix, context, suffix);
343
344 NETDATA_DOUBLE value = rrdvar2number(rv);
345 add_variable(connector_specific_data->write_request, name,
src/libnetdata/http/content_type.c
+8 -8
@@ -42,16 +42,16 @@ static struct {
42
43 // secondary - overlapping with primary
44
45 - { .format = "text/plain", CT_PROMETHEUS, false, "version=0.0.4" },
46 - { .format = "prometheus", CT_PROMETHEUS },
47 - { .format = "text", CT_TEXT_PLAIN },
48 - { .format = "txt", CT_TEXT_PLAIN },
49 - { .format = "json", CT_APPLICATION_JSON },
50 - { .format = "html", CT_TEXT_HTML },
51 - { .format = "xml", CT_APPLICATION_XML },
45 + { .format = "text/plain", CT_PROMETHEUS, true, "version=0.0.4" },
46 + { .format = "prometheus", CT_PROMETHEUS, true },
47 + { .format = "text", CT_TEXT_PLAIN, true },
48 + { .format = "txt", CT_TEXT_PLAIN, true },
49 + { .format = "json", CT_APPLICATION_JSON, true },
50 + { .format = "html", CT_TEXT_HTML, true },
51 + { .format = "xml", CT_APPLICATION_XML, true },
52
53 // terminator
54 - { .format = NULL, CT_TEXT_PLAIN },
54 + { .format = NULL, CT_TEXT_PLAIN, true },
55 };
56
57 HTTP_CONTENT_TYPE content_type_string2id(const char *format) {
src/libnetdata/sanitizers/sanitizers-labels.c
+40 -21
@@ -5,26 +5,26 @@
5 /*
6 * All labels follow these rules:
7 *
8 - * Character Symbol Values Names
9 - * UTF-8 characters UTF-8 yes -> _
10 - * Lower case letter [a-z] yes yes
11 - * Upper case letter [A-Z] yes -> [a-z]
12 - * Digit [0-9] yes yes
13 - * Underscore _ yes yes
14 - * Minus - yes yes
15 - * Plus + yes -> _
16 - * Colon : yes -> _
17 - * Semicolon ; -> : -> _
18 - * Equal = -> : -> _
19 - * Period . yes yes
20 - * Comma , -> . -> .
21 - * Slash / yes yes
22 - * Backslash \ -> / -> /
23 - * At @ yes -> _
24 - * Space yes -> _
25 - * Opening parenthesis ( yes -> _
26 - * Closing parenthesis ) yes -> _
27 - * anything else -> _ -> _
8 + * Character Symbol Names Values
9 + * UTF-8 characters UTF-8 -> _ yes
10 + * Lower case letter [a-z] yes yes
11 + * Upper case letter [A-Z] yes yes
12 + * Digit [0-9] yes yes
13 + * Underscore _ yes yes
14 + * Minus - yes yes
15 + * Plus + -> _ yes
16 + * Colon : -> _ yes
17 + * Semicolon ; -> _ -> :
18 + * Equal = -> _ -> :
19 + * Period . yes yes
20 + * Comma , -> . -> .
21 + * Slash / yes yes
22 + * Backslash \ -> / -> /
23 + * At @ -> _ yes
24 + * Space -> _ yes
25 + * Opening parenthesis ( -> _ yes
26 + * Closing parenthesis ) -> _ yes
27 + * anything else -> _ -> space
28 *
29 * The above rules should allow users to set in tags (indicative):
30 *
@@ -52,6 +52,7 @@
52 *
53 */
54
55 +static unsigned char prometheus_label_names_char_map[256];
56 static unsigned char label_names_char_map[256];
57 static unsigned char label_values_char_map[256] = {
58 [0] = '\0', [1] = ' ', [2] = ' ', [3] = ' ', [4] = ' ', [5] = ' ', [6] = ' ', [7] = ' ', [8] = ' ',
@@ -127,12 +128,30 @@ __attribute__((constructor)) void initialize_labels_keys_char_map(void) {
128 label_names_char_map['('] = '_';
129 label_names_char_map[')'] = '_';
130 label_names_char_map['\\'] = '/';
131 +
132 + // prometheus label names
133 + for(i = 0; i < 256 ;i++) prometheus_label_names_char_map[i] = '_';
134 + for(int s = 'A' ; s <= 'Z' ; s++) prometheus_label_names_char_map[s] = s;
135 + for(int s = 'a' ; s <= 'z' ; s++) prometheus_label_names_char_map[s] = s;
136 + for(int s = '0' ; s <= '9' ; s++) prometheus_label_names_char_map[s] = s;
137 + prometheus_label_names_char_map[0] = '\0';
138 + prometheus_label_names_char_map[':'] = ':';
139 + prometheus_label_names_char_map['_'] = '_';
140 }
141
142 size_t rrdlabels_sanitize_name(char *dst, const char *src, size_t dst_size) {
133 - return text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, label_names_char_map, 0, "", NULL);
143 + size_t rc = text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, label_names_char_map, 0, "", NULL);
144 +
145 + for(size_t i = 0; i < rc ; i++)
146 + if(dst[i] == ' ') dst[i] = '_';
147 +
148 + return rc;
149 }
150
151 size_t rrdlabels_sanitize_value(char *dst, const char *src, size_t dst_size) {
152 return text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, label_values_char_map, 1, "[none]", NULL);
153 }
154 +
155 +size_t prometheus_rrdlabels_sanitize_name(char *dst, const char *src, size_t dst_size) {
156 + return text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, prometheus_label_names_char_map, 0, "", NULL);
157 +}
src/libnetdata/sanitizers/sanitizers-labels.h
+2
@@ -8,4 +8,6 @@
8 size_t rrdlabels_sanitize_name(char *dst, const char *src, size_t dst_size);
9 size_t rrdlabels_sanitize_value(char *dst, const char *src, size_t dst_size);
10
11 +size_t prometheus_rrdlabels_sanitize_name(char *dst, const char *src, size_t dst_size);
12 +
13 #endif //NETDATA_SANITIZERS_LABELS_H