Compare commits

...

5 Commits

Author SHA1 Message Date
Yu Watanabe 819a555bc5
Merge pull request #17399 from afq984/udev-escaped-string
Allow escaped string in udev rules
2020-10-30 09:52:45 +09:00
Yu, Li-Yu bc117ec359 document udev escaped string in udev(7) 2020-10-29 22:57:00 +08:00
Yu, Li-Yu c32d562da5 udev: add fuzzer for udev_rule_parse_value 2020-10-29 20:19:29 +08:00
Yu, Li-Yu 9abfd759d2 udev: test udev_rule_parse_value() 2020-10-29 20:19:29 +08:00
Yu, Li-Yu aea3253e71 udev: escaped string syntax e"..." in rule files
* Existing valid rule files written with KEY="value" are not affected
* Now, KEY=e"value\n" becomes valid. Where `\n` is a newline character
* Escape sequences supported by src/basic/escape.h:cunescape() is
  supported
2020-10-29 20:15:23 +08:00
8 changed files with 1005 additions and 704 deletions

View File

@ -23,7 +23,8 @@
<refpurpose>Dynamic device management</refpurpose>
</refnamediv>
<refsect1><title>Description</title>
<refsect1>
<title>Description</title>
<para>udev supplies the system software with device events, manages permissions
of device nodes and may create additional symlinks in the <filename>/dev/</filename>
directory, or renames network interfaces. The kernel usually just assigns unpredictable
@ -44,7 +45,8 @@
sources is provided by the library libudev.</para>
</refsect1>
<refsect1><title>Rules Files</title>
<refsect1>
<title>Rules Files</title>
<para>The udev rules are read from the files located in the system rules directories
<filename>/usr/lib/udev/rules.d</filename> and <filename>/usr/local/lib/udev/rules.d</filename>, the
volatile runtime directory <filename>/run/udev/rules.d</filename> and the local administration
@ -68,9 +70,11 @@
pointing to the device node, or run a specified program as part of
the event handling.</para>
<para>A rule consists of a comma-separated list of one or more key-value pairs.
Each key has a distinct operation, depending on the used operator. Valid
operators are:</para>
<para>A rule consists of a comma-separated list of one or more key-operator-value expressions.
Each expression has a distinct effect, depending on the key and operator used.</para>
<refsect2>
<title>Operators</title>
<variablelist>
<varlistentry>
<term><literal>==</literal></term>
@ -115,7 +119,26 @@
</listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect2>
<title>Values</title>
<para>Values are written as double quoted strings, such as ("string").
To include a quotation mark (") in the value, precede it by a backslash (\").
Any other occurrences of a character followed by a backslash are not further unescaped.
That is, "\t\n" is treated as four characters:
backslash, lowercase t, backslash, lowercase n.</para>
<para>The string can be prefixed with a lowercase e (e"string\n") to mark the string as
<ulink url="https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences">C-style escaped</ulink>.
For example, e"string\n" is parsed as 7 characters: 6 lowercase letters and a newline.
This can be useful for writting special characters when a kernel driver requires them.</para>
<para>Please note that <constant>NUL</constant> is not allowed in either string variant.</para>
</refsect2>
<refsect2>
<title>Keys</title>
<para>The following key names can be used to match against device properties.
Some of the keys also match against properties of the parent devices in sysfs,
not only the device that has generated the event. If multiple keys that match
@ -789,6 +812,7 @@
</listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<refsect1>

View File

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <string.h>
#include "alloc-util.h"
#include "fuzz.h"
#include "udev-util.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_free_ char *str = NULL;
int r;
char *value = UINT_TO_PTR(0x12345678U);
char *endpos = UINT_TO_PTR(0x87654321U);
assert_se(str = malloc(size + 1));
memcpy(str, data, size);
str[size] = '\0';
r = udev_rule_parse_value(str, &value, &endpos);
if (r < 0) {
/* not modified on failure */
assert_se(value == UINT_TO_PTR(0x12345678U));
assert_se(endpos == UINT_TO_PTR(0x87654321U));
} else {
assert_se(endpos <= str + size);
assert_se(endpos > str + 1);
}
return 0;
}

View File

@ -152,4 +152,8 @@ fuzzers += [
'src/xdg-autostart-generator/xdg-autostart-service.c'],
[],
[]],
[['src/fuzz/fuzz-udev-rule-parse-value.c'],
[libshared],
[]],
]

View File

@ -6,13 +6,16 @@
#include "alloc-util.h"
#include "device-util.h"
#include "env-file.h"
#include "escape.h"
#include "log.h"
#include "macro.h"
#include "parse-util.h"
#include "path-util.h"
#include "signal-util.h"
#include "string-table.h"
#include "string-util.h"
#include "udev-util.h"
#include "utf8.h"
static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = {
[RESOLVE_NAME_NEVER] = "never",
@ -320,3 +323,49 @@ bool device_for_action(sd_device *dev, DeviceAction action) {
return a == action;
}
int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) {
char *i, *j;
int r;
bool is_escaped;
/* value must be double quotated */
is_escaped = str[0] == 'e';
str += is_escaped;
if (str[0] != '"')
return -EINVAL;
str++;
if (!is_escaped) {
/* unescape double quotation '\"'->'"' */
for (i = j = str; *i != '"'; i++, j++) {
if (*i == '\0')
return -EINVAL;
if (i[0] == '\\' && i[1] == '"')
i++;
*j = *i;
}
j[0] = '\0';
} else {
_cleanup_free_ char *unescaped = NULL;
/* find the end position of value */
for (i = str; *i != '"'; i++) {
if (i[0] == '\\')
i++;
if (*i == '\0')
return -EINVAL;
}
i[0] = '\0';
r = cunescape_length(str, i - str, 0, &unescaped);
if (r < 0)
return r;
assert(r <= i - str);
memcpy(str, unescaped, r + 1);
}
*ret_value = str;
*ret_endpos = i + 1;
return 0;
}

View File

@ -32,3 +32,5 @@ int device_wait_for_initialization(sd_device *device, const char *subsystem, use
int device_wait_for_devlink(const char *path, const char *subsystem, usec_t deadline, sd_device **ret);
int device_is_renaming(sd_device *dev);
bool device_for_action(sd_device *dev, DeviceAction action);
int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos);

View File

@ -775,6 +775,10 @@ tests += [
libselinux],
'', 'manual', '-DLOG_REALM=LOG_REALM_UDEV'],
[['src/test/test-udev-util.c'],
[],
[]],
[['src/test/test-id128.c'],
[],
[]],

202
src/test/test-udev-util.c Normal file
View File

@ -0,0 +1,202 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <stdlib.h>
#include <string.h>
#include "macro.h"
#include "string-util.h"
#include "udev-util.h"
static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, int expected_retval) {
_cleanup_free_ char *str = NULL;
char *value = UINT_TO_PTR(0x12345678U);
char *endpos = UINT_TO_PTR(0x87654321U);
assert_se(str = strdup(in));
assert_se(udev_rule_parse_value(str, &value, &endpos) == expected_retval);
if (expected_retval < 0) {
/* not modified on failure */
assert_se(value == UINT_TO_PTR(0x12345678U));
assert_se(endpos == UINT_TO_PTR(0x87654321U));
} else {
assert_se(streq_ptr(value, expected_value));
assert_se(endpos == str + strlen(in));
}
}
static void test_parse_value(void) {
/* input: "valid operand"
* parsed: valid operand
* use the following command to help generate textual C strings:
* python3 -c 'import json; print(json.dumps(input()))' */
test_udev_rule_parse_value_one(
"\"valid operand\"",
"valid operand",
0
);
}
static void test_parse_value_with_backslashes(void) {
/* input: "va'l\'id\"op\"erand"
* parsed: va'l\'id"op"erand */
test_udev_rule_parse_value_one(
"\"va'l\\'id\\\"op\\\"erand\"",
"va'l\\'id\"op\"erand",
0
);
}
static void test_parse_value_no_quotes(void) {
test_udev_rule_parse_value_one(
"no quotes",
0,
-EINVAL
);
}
static void test_parse_value_noescape(void) {
test_udev_rule_parse_value_one(
"\"\\\\a\\b\\x\\y\"",
"\\\\a\\b\\x\\y",
0
);
}
static void test_parse_value_nul(void) {
test_udev_rule_parse_value_one(
"\"reject\0nul\"",
0,
-EINVAL
);
}
static void test_parse_value_escape_nothing(void) {
/* input: e"" */
test_udev_rule_parse_value_one(
"e\"\"",
"",
0
);
}
static void test_parse_value_escape_nothing2(void) {
/* input: e"1234" */
test_udev_rule_parse_value_one(
"e\"1234\"",
"1234",
0
);
}
static void test_parse_value_escape_double_quote(void) {
/* input: e"\"" */
test_udev_rule_parse_value_one(
"e\"\\\"\"",
"\"",
0
);
}
static void test_parse_value_escape_backslash(void) {
/* input: e"\ */
test_udev_rule_parse_value_one(
"e\"\\",
0,
-EINVAL
);
/* input: e"\" */
test_udev_rule_parse_value_one(
"e\"\\\"",
0,
-EINVAL
);
/* input: e"\\" */
test_udev_rule_parse_value_one(
"e\"\\\\\"",
"\\",
0
);
/* input: e"\\\" */
test_udev_rule_parse_value_one(
"e\"\\\\\\\"",
0,
-EINVAL
);
/* input: e"\\\"" */
test_udev_rule_parse_value_one(
"e\"\\\\\\\"\"",
"\\\"",
0
);
/* input: e"\\\\" */
test_udev_rule_parse_value_one(
"e\"\\\\\\\\\"",
"\\\\",
0
);
}
static void test_parse_value_newline(void) {
/* input: e"operand with newline\n" */
test_udev_rule_parse_value_one(
"e\"operand with newline\\n\"",
"operand with newline\n",
0
);
}
static void test_parse_value_escaped(void) {
/* input: e"single\rcharacter\t\aescape\bsequence" */
test_udev_rule_parse_value_one(
"e\"single\\rcharacter\\t\\aescape\\bsequence\"",
"single\rcharacter\t\aescape\bsequence",
0
);
}
static void test_parse_value_invalid_escape(void) {
/* input: e"reject\invalid escape sequence" */
test_udev_rule_parse_value_one(
"e\"reject\\invalid escape sequence",
0,
-EINVAL
);
}
static void test_parse_value_invalid_termination(void) {
/* input: e"\ */
test_udev_rule_parse_value_one(
"e\"\\",
0,
-EINVAL
);
}
static void test_parse_value_unicode(void) {
/* input: "s\u1d1c\u1d04\u029c \u1d1c\u0274\u026a\u1d04\u1d0f\u1d05\u1d07 \U0001d568\U0001d560\U0001d568" */
test_udev_rule_parse_value_one(
"e\"s\\u1d1c\\u1d04\\u029c \\u1d1c\\u0274\\u026a\\u1d04\\u1d0f\\u1d05\\u1d07 \\U0001d568\\U0001d560\\U0001d568\"",
"s\xe1\xb4\x9c\xe1\xb4\x84\xca\x9c \xe1\xb4\x9c\xc9\xb4\xc9\xaa\xe1\xb4\x84\xe1\xb4\x8f\xe1\xb4\x85\xe1\xb4\x87 \xf0\x9d\x95\xa8\xf0\x9d\x95\xa0\xf0\x9d\x95\xa8",
0
);
}
int main(int argc, char **argv) {
test_parse_value();
test_parse_value_with_backslashes();
test_parse_value_no_quotes();
test_parse_value_nul();
test_parse_value_noescape();
test_parse_value_escape_nothing();
test_parse_value_escape_nothing2();
test_parse_value_escape_double_quote();
test_parse_value_escape_backslash();
test_parse_value_newline();
test_parse_value_escaped();
test_parse_value_invalid_escape();
test_parse_value_invalid_termination();
test_parse_value_unicode();
return EXIT_SUCCESS;
}

View File

@ -990,8 +990,9 @@ static UdevRuleOperatorType parse_operator(const char *op) {
}
static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) {
char *key_begin, *key_end, *attr, *tmp, *value, *i, *j;
char *key_begin, *key_end, *attr, *tmp;
UdevRuleOperatorType op;
int r;
assert(line);
assert(*line);
@ -1031,30 +1032,14 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
key_end[0] = '\0';
tmp += op == OP_ASSIGN ? 1 : 2;
value = skip_leading_chars(tmp, NULL);
tmp = skip_leading_chars(tmp, NULL);
r = udev_rule_parse_value(tmp, ret_value, line);
if (r < 0)
return r;
/* value must be double quotated */
if (value[0] != '"')
return -EINVAL;
value++;
/* unescape double quotation '\"' -> '"' */
for (i = j = value; ; i++, j++) {
if (*i == '"')
break;
if (*i == '\0')
return -EINVAL;
if (i[0] == '\\' && i[1] == '"')
i++;
*j = *i;
}
j[0] = '\0';
*line = i+1;
*ret_key = key_begin;
*ret_attr = attr;
*ret_op = op;
*ret_value = value;
return 1;
}