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