From 1844aacc837bf81cb1959fa65f2e52dcc70a0cae Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 11 Nov 2021 23:31:23 +0000 Subject: [uri] Retain original encodings for path, query, and fragment fields iPXE decodes any percent-encoded characters during the URI parsing stage, thereby allowing protocol implementations to consume the raw field values directly without further decoding. When reconstructing a URI string for use in an HTTP request line, the percent-encoding is currently reapplied in a reversible way: we guarantee that our reconstructed URI string could be decoded to give the same raw field values. This technically violates RFC3986, which states that "URIs that differ in the replacement of a reserved character with its corresponding percent-encoded octet are not equivalent". Experiments show that several HTTP server applications will attach meaning to the choice of whether or not a particular character was percent-encoded, even when the percent-encoding is unnecessary from the perspective of parsing the URI into its component fields. Fix by storing the originally encoded substrings for the path, query, and fragment fields and using these original encoded versions when reconstructing a URI string. The path field is also stored as a decoded string, for use by protocols such as TFTP that communicate using raw strings rather than URI-encoded strings. All other fields (such as the username and password) continue to be stored only in their decoded versions since nothing ever needs to know the originally encoded versions of these fields. Signed-off-by: Michael Brown --- src/tests/uri_test.c | 53 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) (limited to 'src/tests') diff --git a/src/tests/uri_test.c b/src/tests/uri_test.c index 92c2f9037..929ab3632 100644 --- a/src/tests/uri_test.c +++ b/src/tests/uri_test.c @@ -149,8 +149,10 @@ static void uri_okx ( struct uri *uri, struct uri *expected, const char *file, okx ( uristrcmp ( uri->host, expected->host ) == 0, file, line ); okx ( uristrcmp ( uri->port, expected->port ) == 0, file, line ); okx ( uristrcmp ( uri->path, expected->path ) == 0, file, line ); - okx ( uristrcmp ( uri->query, expected->query ) == 0, file, line ); - okx ( uristrcmp ( uri->fragment, expected->fragment ) == 0, file, line); + okx ( uristrcmp ( uri->epath, expected->epath ) == 0, file, line ); + okx ( uristrcmp ( uri->equery, expected->equery ) == 0, file, line ); + okx ( uristrcmp ( uri->efragment, expected->efragment ) == 0, + file, line); okx ( uri->params == expected->params, file, line ); } #define uri_ok( uri, expected ) uri_okx ( uri, expected, __FILE__, __LINE__ ) @@ -490,25 +492,33 @@ static struct uri_test uri_empty = { /** Basic HTTP URI */ static struct uri_test uri_boot_ipxe_org = { "http://boot.ipxe.org/demo/boot.php", - { .scheme = "http", .host = "boot.ipxe.org", .path = "/demo/boot.php" } + { .scheme = "http", .host = "boot.ipxe.org", + .path = "/demo/boot.php", .epath = "/demo/boot.php" }, }; /** Basic opaque URI */ static struct uri_test uri_mailto = { "mailto:ipxe-devel@lists.ipxe.org", - { .scheme = "mailto", .opaque = "ipxe-devel@lists.ipxe.org" } + { .scheme = "mailto", .opaque = "ipxe-devel@lists.ipxe.org" }, +}; + +/** Basic host-only URI */ +static struct uri_test uri_host = { + "http://boot.ipxe.org", + { .scheme = "http", .host = "boot.ipxe.org" }, }; /** Basic path-only URI */ static struct uri_test uri_path = { "/var/lib/tftpboot/pxelinux.0", - { .path = "/var/lib/tftpboot/pxelinux.0" }, + { .path = "/var/lib/tftpboot/pxelinux.0", + .epath ="/var/lib/tftpboot/pxelinux.0" }, }; /** Path-only URI with escaped characters */ static struct uri_test uri_path_escaped = { "/hello%20world%3F", - { .path = "/hello world?" }, + { .path = "/hello world?", .epath = "/hello%20world%3F" }, }; /** HTTP URI with all the trimmings */ @@ -521,8 +531,9 @@ static struct uri_test uri_http_all = { .host = "example.com", .port = "3001", .path = "/~foo/cgi-bin/foo.pl", - .query = "a=b&c=d", - .fragment = "bit", + .epath = "/~foo/cgi-bin/foo.pl", + .equery = "a=b&c=d", + .efragment = "bit", }, }; @@ -533,8 +544,9 @@ static struct uri_test uri_http_escaped = { .scheme = "https", .host = "test.ipxe.org", .path = "/wtf?\n", - .query = "kind#of/uri is", - .fragment = "this?", + .epath = "/wtf%3F%0A", + .equery = "kind%23of/uri%20is", + .efragment = "this%3F", }, }; @@ -550,8 +562,9 @@ static struct uri_test uri_http_escaped_improper = { .scheme = "https", .host = "test.ipxe.org", .path = "/wtf?\n", - .query = "kind#of/uri is", - .fragment = "this?", + .epath = "/wt%66%3f\n", + .equery = "kind%23of/uri is", + .efragment = "this?", }, }; @@ -562,6 +575,7 @@ static struct uri_test uri_ipv6 = { .scheme = "http", .host = "[2001:ba8:0:1d4::6950:5845]", .path = "/", + .epath = "/", }, }; @@ -573,6 +587,7 @@ static struct uri_test uri_ipv6_port = { .host = "[2001:ba8:0:1d4::6950:5845]", .port = "8001", .path = "/boot", + .epath = "/boot", }, }; @@ -583,6 +598,7 @@ static struct uri_test uri_ipv6_local = { .scheme = "http", .host = "[fe80::69ff:fe50:5845%net0]", .path = "/ipxe", + .epath = "/ipxe", }, }; @@ -598,6 +614,7 @@ static struct uri_test uri_ipv6_local_non_conforming = { .scheme = "http", .host = "[fe80::69ff:fe50:5845%net0]", .path = "/ipxe", + .epath = "/ipxe", }, }; @@ -625,6 +642,7 @@ static struct uri_test uri_file_absolute = { { .scheme = "file", .path = "/boot/script.ipxe", + .epath = "/boot/script.ipxe", }, }; @@ -635,6 +653,7 @@ static struct uri_test uri_file_volume = { .scheme = "file", .host = "hpilo", .path = "/boot/script.ipxe", + .epath = "/boot/script.ipxe", }, }; @@ -736,6 +755,7 @@ static struct uri_pxe_test uri_pxe_absolute = { .scheme = "http", .host = "not.a.tftp", .path = "/uri", + .epath = "/uri", }, "http://not.a.tftp/uri", }; @@ -754,6 +774,7 @@ static struct uri_pxe_test uri_pxe_absolute_path = { .scheme = "tftp", .host = "192.168.0.2", .path = "//absolute/path", + .epath = "//absolute/path", }, "tftp://192.168.0.2//absolute/path", }; @@ -772,6 +793,7 @@ static struct uri_pxe_test uri_pxe_relative_path = { .scheme = "tftp", .host = "192.168.0.3", .path = "/relative/path", + .epath = "/relative/path", }, "tftp://192.168.0.3/relative/path", }; @@ -790,8 +812,9 @@ static struct uri_pxe_test uri_pxe_icky = { .scheme = "tftp", .host = "10.0.0.6", .path = "/C:\\tftpboot\\icky#path", + .epath = "/C:\\tftpboot\\icky#path", }, - "tftp://10.0.0.6/C%3A\\tftpboot\\icky%23path", + "tftp://10.0.0.6/C:\\tftpboot\\icky#path", }; /** PXE URI with custom port */ @@ -810,6 +833,7 @@ static struct uri_pxe_test uri_pxe_port = { .host = "192.168.0.1", .port = "4069", .path = "//another/path", + .epath = "//another/path", }, "tftp://192.168.0.1:4069//another/path", }; @@ -873,6 +897,7 @@ static struct uri_params_test uri_params = { .scheme = "http", .host = "boot.ipxe.org", .path = "/demo/boot.php", + .epath = "/demo/boot.php", }, NULL, uri_params_list, @@ -902,6 +927,7 @@ static struct uri_params_test uri_named_params = { .host = "192.168.100.4", .port = "3001", .path = "/register", + .epath = "/register", }, "foo", uri_named_params_list, @@ -917,6 +943,7 @@ static void uri_test_exec ( void ) { uri_parse_format_dup_ok ( &uri_empty ); uri_parse_format_dup_ok ( &uri_boot_ipxe_org ); uri_parse_format_dup_ok ( &uri_mailto ); + uri_parse_format_dup_ok ( &uri_host ); uri_parse_format_dup_ok ( &uri_path ); uri_parse_format_dup_ok ( &uri_path_escaped ); uri_parse_format_dup_ok ( &uri_http_all ); -- cgit v1.2.3-55-g7522 From f43c2fd69749bb9a44f2a3ab61b6735938432b52 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 4 Jan 2022 13:31:15 +0000 Subject: [settings] Support formatting UUIDs as little-endian GUIDs The RFC4122 specification defines UUIDs as being in network byte order, but an unfortunately significant amount of (mostly Microsoft) software treats them as having the first three fields in little-endian byte order. In an ideal world, any server-side software that compares UUIDs for equality would perform an endian-insensitive comparison (analogous to comparing strings for equality using a case-insensitive comparison), and would therefore not care about byte order differences. Define a setting type name ":guid" to allow a UUID setting to be formatted in little-endian order, to simplify interoperability with server-side software that expects such a formatting. Signed-off-by: Michael Brown --- src/core/settings.c | 23 ++++++++++++++++++----- src/include/ipxe/settings.h | 1 + src/interface/smbios/smbios_settings.c | 3 ++- src/tests/settings_test.c | 10 ++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) (limited to 'src/tests') diff --git a/src/core/settings.c b/src/core/settings.c index 430cdc84b..fcdf98d2b 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -2199,7 +2199,7 @@ const struct setting_type setting_type_base64 __setting_type = { }; /** - * Format UUID setting value + * Format UUID/GUID setting value * * @v type Setting type * @v raw Raw setting value @@ -2208,17 +2208,24 @@ const struct setting_type setting_type_base64 __setting_type = { * @v len Length of buffer * @ret len Length of formatted value, or negative error */ -static int format_uuid_setting ( const struct setting_type *type __unused, +static int format_uuid_setting ( const struct setting_type *type, const void *raw, size_t raw_len, char *buf, size_t len ) { - const union uuid *uuid = raw; + union uuid uuid; /* Range check */ - if ( raw_len != sizeof ( *uuid ) ) + if ( raw_len != sizeof ( uuid ) ) return -ERANGE; + /* Copy value */ + memcpy ( &uuid, raw, sizeof ( uuid ) ); + + /* Mangle GUID byte ordering */ + if ( type == &setting_type_guid ) + uuid_mangle ( &uuid ); + /* Format value */ - return snprintf ( buf, len, "%s", uuid_ntoa ( uuid ) ); + return snprintf ( buf, len, "%s", uuid_ntoa ( &uuid ) ); } /** UUID setting type */ @@ -2227,6 +2234,12 @@ const struct setting_type setting_type_uuid __setting_type = { .format = format_uuid_setting, }; +/** GUID setting type */ +const struct setting_type setting_type_guid __setting_type = { + .name = "guid", + .format = format_uuid_setting, +}; + /** * Format PCI bus:dev.fn setting value * diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h index f463e6674..e042b9758 100644 --- a/src/include/ipxe/settings.h +++ b/src/include/ipxe/settings.h @@ -426,6 +426,7 @@ extern const struct setting_type setting_type_hexhyp __setting_type; extern const struct setting_type setting_type_hexraw __setting_type; extern const struct setting_type setting_type_base64 __setting_type; extern const struct setting_type setting_type_uuid __setting_type; +extern const struct setting_type setting_type_guid __setting_type; extern const struct setting_type setting_type_busdevfn __setting_type; extern const struct setting_type setting_type_dnssl __setting_type; diff --git a/src/interface/smbios/smbios_settings.c b/src/interface/smbios/smbios_settings.c index 2d571f2e4..ec31b43f2 100644 --- a/src/interface/smbios/smbios_settings.c +++ b/src/interface/smbios/smbios_settings.c @@ -140,7 +140,8 @@ static int smbios_fetch ( struct settings *settings __unused, * is 2.6 or higher; we match this behaviour. */ raw = &buf[tag_offset]; - if ( ( setting->type == &setting_type_uuid ) && + if ( ( ( setting->type == &setting_type_uuid ) || + ( setting->type == &setting_type_guid ) ) && ( tag_len == sizeof ( uuid ) ) && ( smbios_version() >= SMBIOS_VERSION ( 2, 6 ) ) ) { DBG ( "SMBIOS detected mangled UUID\n" ); diff --git a/src/tests/settings_test.c b/src/tests/settings_test.c index 828901b06..5da7eb008 100644 --- a/src/tests/settings_test.c +++ b/src/tests/settings_test.c @@ -250,6 +250,12 @@ static struct setting test_uuid_setting = { .type = &setting_type_uuid, }; +/** Test GUID setting type */ +static struct setting test_guid_setting = { + .name = "test_guid", + .type = &setting_type_guid, +}; + /** Test PCI bus:dev.fn setting type */ static struct setting test_busdevfn_setting = { .name = "test_busdevfn", @@ -419,6 +425,10 @@ static void settings_test_exec ( void ) { RAW ( 0x1a, 0x6a, 0x74, 0x9d, 0x0e, 0xda, 0x46, 0x1a,0xa8, 0x7a, 0x7c, 0xfe, 0x4f, 0xca, 0x4a, 0x57 ), "1a6a749d-0eda-461a-a87a-7cfe4fca4a57" ); + fetchf_ok ( &test_settings, &test_guid_setting, + RAW ( 0x1a, 0x6a, 0x74, 0x9d, 0x0e, 0xda, 0x46, 0x1a,0xa8, + 0x7a, 0x7c, 0xfe, 0x4f, 0xca, 0x4a, 0x57 ), + "9d746a1a-da0e-1a46-a87a-7cfe4fca4a57" ); /* "busdevfn" setting type (no store capability) */ fetchf_ok ( &test_settings, &test_busdevfn_setting, -- cgit v1.2.3-55-g7522 From e814d33900992e034a8c3ddec2c65463c5206090 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 13 Jan 2022 14:53:36 +0000 Subject: [uri] Allow for relative URIs that include colons within the path RFC3986 allows for colons to appear within the path component of a relative URI, but iPXE will currently parse such URIs incorrectly by interpreting the text before the colon as the URI scheme. Fix by checking for valid characters when identifying the URI scheme. Deliberately deviate from the RFC3986 definition of valid characters by accepting "_" (which was incorrectly used in the iPXE-specific "ib_srp" URI scheme and so must be accepted for compatibility with existing deployments), and by omitting the code to check for characters that are not used in any URI scheme supported by iPXE. Reported-by: Ignat Korchagin Signed-off-by: Michael Brown --- src/core/uri.c | 15 ++++++++++----- src/tests/uri_test.c | 10 ++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'src/tests') diff --git a/src/core/uri.c b/src/core/uri.c index a0f79e9ec..b82472ef0 100644 --- a/src/core/uri.c +++ b/src/core/uri.c @@ -334,8 +334,15 @@ struct uri * parse_uri ( const char *uri_string ) { uri->efragment = tmp; } - /* Identify absolute/relative URI */ - if ( ( tmp = strchr ( raw, ':' ) ) ) { + /* Identify absolute URIs */ + epath = raw; + for ( tmp = raw ; ; tmp++ ) { + /* Possible scheme character (for our URI schemes) */ + if ( isalpha ( *tmp ) || ( *tmp == '-' ) || ( *tmp == '_' ) ) + continue; + /* Invalid scheme character or NUL: is a relative URI */ + if ( *tmp != ':' ) + break; /* Absolute URI: identify hierarchical/opaque */ uri->scheme = raw; *(tmp++) = '\0'; @@ -347,9 +354,7 @@ struct uri * parse_uri ( const char *uri_string ) { uri->opaque = tmp; epath = NULL; } - } else { - /* Relative URI */ - epath = raw; + break; } /* If we don't have a path (i.e. we have an absolute URI with diff --git a/src/tests/uri_test.c b/src/tests/uri_test.c index 929ab3632..338f479cd 100644 --- a/src/tests/uri_test.c +++ b/src/tests/uri_test.c @@ -657,6 +657,15 @@ static struct uri_test uri_file_volume = { }, }; +/** Relative URI with colons in path */ +static struct uri_test uri_colons = { + "/boot/52:54:00:12:34:56/boot.ipxe", + { + .path = "/boot/52:54:00:12:34:56/boot.ipxe", + .epath = "/boot/52:54:00:12:34:56/boot.ipxe", + }, +}; + /** URI with port number */ static struct uri_port_test uri_explicit_port = { "http://192.168.0.1:8080/boot.php", @@ -957,6 +966,7 @@ static void uri_test_exec ( void ) { uri_parse_format_dup_ok ( &uri_file_relative ); uri_parse_format_dup_ok ( &uri_file_absolute ); uri_parse_format_dup_ok ( &uri_file_volume ); + uri_parse_format_dup_ok ( &uri_colons ); /** URI port number tests */ uri_port_ok ( &uri_explicit_port ); -- cgit v1.2.3-55-g7522 From 7e9631b60fdcb02f05a80983ca68c10f26e4ab33 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 28 Feb 2022 14:41:45 +0000 Subject: [utf8] Add UTF-8 accumulation self-tests Signed-off-by: Michael Brown --- src/tests/tests.c | 1 + src/tests/utf8_test.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/tests/utf8_test.c (limited to 'src/tests') diff --git a/src/tests/tests.c b/src/tests/tests.c index 1cc4c81e8..4dd4adf84 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -75,3 +75,4 @@ REQUIRE_OBJECT ( pem_test ); REQUIRE_OBJECT ( ntlm_test ); REQUIRE_OBJECT ( zlib_test ); REQUIRE_OBJECT ( gzip_test ); +REQUIRE_OBJECT ( utf8_test ); diff --git a/src/tests/utf8_test.c b/src/tests/utf8_test.c new file mode 100644 index 000000000..1996915cb --- /dev/null +++ b/src/tests/utf8_test.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * UTF-8 Unicode encoding tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include +#include +#include + +/** A UTF-8 accumulation test */ +struct utf8_accumulate_test { + /** UTF-8 byte string */ + const char *bytes; + /** Expected character sequence */ + const unsigned int *expected; + /** Length */ + size_t len; +}; + +/** Define inline data */ +#define DATA(...) { __VA_ARGS__ } + +/** Define a UTF-8 accumulation test */ +#define UTF8_ACCUMULATE( name, BYTES, EXPECTED ) \ + static const char name ## _bytes[] = BYTES; \ + static const unsigned int name ## _expected[] = EXPECTED; \ + static struct utf8_accumulate_test name = { \ + .bytes = name ## _bytes, \ + .expected = name ## _expected, \ + .len = ( sizeof ( name ## _expected ) / \ + sizeof ( name ## _expected[0] ) ), \ + }; + +/** Basic ASCII test */ +UTF8_ACCUMULATE ( ascii, "Hello world!", + DATA ( 'H', 'e', 'l', 'l', 'o', ' ', + 'w', 'o', 'r', 'l', 'd', '!' ) ); + +/** Multi-byte character test */ +UTF8_ACCUMULATE ( multibyte, "Héllô wörld 🥳", + DATA ( 'H', 0, L'é', 'l', 'l', 0, L'ô', ' ', + 'w', 0, L'ö', 'r', 'l', 'd', ' ', + 0, 0, 0, 0x1f973 ) ); + +/** Stray continuation byte test */ +UTF8_ACCUMULATE ( stray_continuation, + DATA ( 'a', 0x81, 'b', 0xc3, 0x82, 0x83, 'c' ), + DATA ( 'a', 0xfffd, 'b', 0, 0xc2, 0xfffd, 'c' ) ); + +/** Missing continuation byte test */ +UTF8_ACCUMULATE ( missing_continuation, + DATA ( 'a', 0xc3, 'b', 0xe1, 0x86, 0xc3, 0x89, 'c' ), + DATA ( 'a', 0, 'b', 0, 0, 0, 0xc9, 'c' ) ); + +/** Illegal two-byte sequence test */ +UTF8_ACCUMULATE ( illegal_two, + DATA ( 'a', 0xc2, 0x80, 'b', 0xc1, 0xbf, 'c', 0xc0, 0x80, + 'd' ), + DATA ( 'a', 0, 0x80, 'b', 0, 0xfffd, 'c', 0, 0xfffd, 'd' ) ); + +/** Illegal three-byte sequence test */ +UTF8_ACCUMULATE ( illegal_three, + DATA ( 'a', 0xe0, 0xa0, 0x80, 'b', 0xe0, 0x9f, 0xbf, 'c', + 0xe0, 0x80, 0x80, 'd' ), + DATA ( 'a', 0, 0, 0x800, 'b', 0, 0, 0xfffd, 'c', + 0, 0, 0xfffd, 'd' ) ); + +/** Illegal four-byte sequence test */ +UTF8_ACCUMULATE ( illegal_four, + DATA ( 'a', 0xf0, 0x90, 0x80, 0x80, 'b', 0xf0, 0x8f, 0xbf, + 0xbf, 'c', 0xf0, 0x80, 0x80, 0x80, 'd' ), + DATA ( 'a', 0, 0, 0, 0x10000, 'b', 0, 0, 0, 0xfffd, 'c', + 0, 0, 0, 0xfffd, 'd' ) ); + +/** Illegal overlength sequence test */ +UTF8_ACCUMULATE ( illegal_length, + DATA ( 'a', 0xf8, 0xbf, 0xbf, 0xbf, 0xbf, 'b', 0xfc, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 'c', 0xfe, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 'd', 0xff, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 'e' ), + DATA ( 'a', 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 'b', + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 'c', + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 'd', 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 'e' ) ); + +/** + * Report UTF-8 accumulation test result + * + * @v test UTF-8 accumulation test + * @v file Test code file + * @v line Test code line + */ +static void utf8_accumulate_okx ( struct utf8_accumulate_test *test, + const char *file, unsigned int line ) { + struct utf8_accumulator utf8; + unsigned int character; + unsigned int i; + + /* Initialise accumulator */ + memset ( &utf8, 0, sizeof ( utf8 ) ); + + /* Test each byte in turn */ + for ( i = 0 ; i < test->len ; i++ ) { + character = utf8_accumulate ( &utf8, test->bytes[i] ); + DBGC ( test, "UTF8 byte %02x character %02x\n", + test->bytes[i], character ); + okx ( character == test->expected[i], file, line ); + } +} +#define utf8_accumulate_ok( test ) \ + utf8_accumulate_okx ( test, __FILE__, __LINE__ ) + +/** + * Perform UTF-8 self-test + * + */ +static void utf8_test_exec ( void ) { + + /* Accumulation tests */ + utf8_accumulate_ok ( &ascii ); + utf8_accumulate_ok ( &multibyte ); + utf8_accumulate_ok ( &stray_continuation ); + utf8_accumulate_ok ( &missing_continuation ); + utf8_accumulate_ok ( &illegal_two ); + utf8_accumulate_ok ( &illegal_three ); + utf8_accumulate_ok ( &illegal_four ); + utf8_accumulate_ok ( &illegal_length ); +} + +/** UTF-8 self-test */ +struct self_test utf8_test __self_test = { + .name = "utf8", + .exec = utf8_test_exec, +}; -- cgit v1.2.3-55-g7522 From 614c3f43a12cd6be6d79d7a8a63cfa603ff9d49f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 23 Mar 2022 14:57:53 +0000 Subject: [acpi] Add MAC address extraction self-tests Signed-off-by: Michael Brown --- src/tests/acpi_test.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/tests/tests.c | 1 + 2 files changed, 239 insertions(+) create mode 100644 src/tests/acpi_test.c (limited to 'src/tests') diff --git a/src/tests/acpi_test.c b/src/tests/acpi_test.c new file mode 100644 index 000000000..972067ee2 --- /dev/null +++ b/src/tests/acpi_test.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2022 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * ACPI tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include +#include +#include +#include + +/** An ACPI test table signature */ +union acpi_test_signature { + /** String */ + char str[4]; + /** Raw value */ + uint32_t raw; +}; + +/** An ACPI test table */ +struct acpi_test_table { + /** Signature */ + union acpi_test_signature signature; + /** Table content */ + const void *data; +}; + +/** An ACPI test table set */ +struct acpi_test_tables { + /** Tables */ + struct acpi_test_table **table; + /** Number of tables */ + unsigned int count; +}; + +/** An ACPI MAC extraction test */ +struct acpi_mac_test { + /** ACPI test table set */ + struct acpi_test_tables *tables; + /** Expected MAC address */ + uint8_t expected[ETH_ALEN]; +}; + +/** Define inline data */ +#define DATA(...) { __VA_ARGS__ } + +/** Define an ACPI test table */ +#define ACPI_TABLE( name, SIGNATURE, DATA ) \ + static const uint8_t name ## _data[] = DATA; \ + static struct acpi_test_table name = { \ + .signature = { \ + .str = SIGNATURE, \ + }, \ + .data = name ## _data, \ + } + +/** Define an ACPI test table set */ +#define ACPI_TABLES( name, ... ) \ + static struct acpi_test_table * name ## _table[] = \ + { __VA_ARGS__ }; \ + static struct acpi_test_tables name = { \ + .table = name ## _table, \ + .count = ( sizeof ( name ## _table ) / \ + sizeof ( name ## _table[0] ) ), \ + } + +/** Define an ACPI MAC extraction test */ +#define ACPI_MAC( name, TABLES, EXPECTED ) \ + static struct acpi_mac_test name = { \ + .tables = TABLES, \ + .expected = EXPECTED, \ + } + +/** "AMAC" SSDT + * + * DefinitionBlock ("", "SSDT", 2, "", "", 0x0) { + * Scope (\_SB) { + * Method (HW00, 0, Serialized) { Return(0) } + * Method (AMAC, 0, Serialized) { ToString("_AUXMAC_#525400aabbcc#") } + * Method (HW42, 0, Serialized) { Return(42) } + * } + * } + */ +ACPI_TABLE ( amac_ssdt, "SSDT", + DATA ( 0x53, 0x53, 0x44, 0x54, 0x5d, 0x00, 0x00, 0x00, 0x02, + 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x04, 0x06, 0x21, 0x20, + 0x10, 0x38, 0x5c, 0x5f, 0x53, 0x42, 0x5f, 0x14, 0x08, + 0x48, 0x57, 0x30, 0x30, 0x08, 0xa4, 0x00, 0x14, 0x1e, + 0x41, 0x4d, 0x41, 0x43, 0x08, 0x0d, 0x5f, 0x41, 0x55, + 0x58, 0x4d, 0x41, 0x43, 0x5f, 0x23, 0x35, 0x32, 0x35, + 0x34, 0x30, 0x30, 0x61, 0x61, 0x62, 0x62, 0x63, 0x63, + 0x23, 0x00, 0x14, 0x09, 0x48, 0x57, 0x34, 0x32, 0x08, + 0xa4, 0x0a, 0x2a ) ); + +/** "AMAC" test tables */ +ACPI_TABLES ( amac_tables, &amac_ssdt ); + +/** "AMAC" test */ +ACPI_MAC ( amac, &amac_tables, + DATA ( 0x52, 0x54, 0x00, 0xaa, 0xbb, 0xcc ) ); + +/** "MACA" SSDT1 (does not contain AUXMAC) */ +ACPI_TABLE ( maca_ssdt1, "SSDT", + DATA ( 0x53, 0x53, 0x44, 0x54, 0x3e, 0x00, 0x00, 0x00, 0x02, + 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x04, 0x06, 0x21, 0x20, + 0x10, 0x19, 0x5c, 0x5f, 0x53, 0x42, 0x5f, 0x14, 0x08, + 0x48, 0x57, 0x30, 0x30, 0x08, 0xa4, 0x00, 0x14, 0x09, + 0x48, 0x57, 0x34, 0x32, 0x08, 0xa4, 0x0a, 0x2a ) ); + +/** "MACA" SSDT2 (contains AUXMAC) */ +ACPI_TABLE ( maca_ssdt2, "SSDT", + DATA ( 0x53, 0x53, 0x44, 0x54, 0x54, 0x00, 0x00, 0x00, 0x02, + 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x04, 0x06, 0x21, 0x20, + 0x10, 0x2f, 0x5c, 0x5f, 0x53, 0x42, 0x5f, 0x14, 0x1e, + 0x4d, 0x41, 0x43, 0x41, 0x08, 0x0d, 0x5f, 0x41, 0x55, + 0x58, 0x4d, 0x41, 0x43, 0x5f, 0x23, 0x35, 0x32, 0x35, + 0x34, 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, + 0x23, 0x00, 0x14, 0x09, 0x48, 0x57, 0x39, 0x39, 0x08, + 0xa4, 0x0a, 0x63 ) ); + +/** "MACA" test tables */ +ACPI_TABLES ( maca_tables, &maca_ssdt1, &maca_ssdt2 ); + +/** "MACA" test */ +ACPI_MAC ( maca, &maca_tables, + DATA ( 0x52, 0x54, 0x00, 0x11, 0x22, 0x33 ) ); + +/** Current ACPI test table set */ +static struct acpi_test_tables *acpi_test_tables; + +/** + * Locate ACPI test table + * + * @v signature Requested table signature + * @v index Requested index of table with this signature + * @ret table Table, or UNULL if not found + */ +static userptr_t acpi_test_find ( uint32_t signature, unsigned int index ) { + struct acpi_test_table *table; + unsigned int i; + + /* Fail if no test tables are installed */ + if ( ! acpi_test_tables ) + return UNULL; + + /* Scan through test tables */ + for ( i = 0 ; i < acpi_test_tables->count ; i++ ) { + table = acpi_test_tables->table[i]; + if ( ( signature == le32_to_cpu ( table->signature.raw ) ) && + ( index-- == 0 ) ) { + return virt_to_user ( table->data ); + } + } + + return UNULL; +} + +/** Override ACPI table finder */ +typeof ( acpi_find ) *acpi_finder = acpi_test_find; + +/** + * Report ACPI MAC extraction test result + * + * @v test ACPI MAC extraction test + * @v file Test code file + * @v line Test code line + */ +static void acpi_mac_okx ( struct acpi_mac_test *test, + const char *file, unsigned int line ) { + uint8_t mac[ETH_ALEN]; + int rc; + + /* Set test table set */ + acpi_test_tables = test->tables; + + /* Extract MAC address */ + rc = acpi_mac ( mac ); + okx ( rc == 0, file, line ); + + /* Check extracted MAC address */ + okx ( memcmp ( mac, test->expected, ETH_ALEN ) == 0, file, line ); + + /* Clear test table set */ + acpi_test_tables = NULL; +} +#define acpi_mac_ok( test ) \ + acpi_mac_okx ( test, __FILE__, __LINE__ ) + +/** + * Perform ACPI self-test + * + */ +static void acpi_test_exec ( void ) { + + /* MAC extraction tests */ + acpi_mac_ok ( &amac ); + acpi_mac_ok ( &maca ); +} + +/** ACPI self-test */ +struct self_test acpi_test __self_test = { + .name = "acpi", + .exec = acpi_test_exec, +}; diff --git a/src/tests/tests.c b/src/tests/tests.c index 4dd4adf84..5dbbac1b1 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -76,3 +76,4 @@ REQUIRE_OBJECT ( ntlm_test ); REQUIRE_OBJECT ( zlib_test ); REQUIRE_OBJECT ( gzip_test ); REQUIRE_OBJECT ( utf8_test ); +REQUIRE_OBJECT ( acpi_test ); -- cgit v1.2.3-55-g7522 From f58b5109f46088bdbb5345a9d94b636c54345bdf Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 23 Mar 2022 15:02:17 +0000 Subject: [acpi] Support the "_RTXMAC_" format for ACPI-based MAC addresses Some newer HP products expose the host-based MAC (HBMAC) address using an ACPI method named "RTMA" returning a part-binary string of the form "_RTXMAC_##", where "" comprises the raw MAC address bytes. Extend the existing support to handle this format alongside the older "_AUXMAC_" format (which uses a base16-encoded MAC address). Reported-by: Andreas Hammarskjöld Tested-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/core/acpimac.c | 153 +++++++++++++++++++++++++++++++++++++++++--------- src/tests/acpi_test.c | 19 +++++++ 2 files changed, 144 insertions(+), 28 deletions(-) (limited to 'src/tests') diff --git a/src/core/acpimac.c b/src/core/acpimac.c index 1cc8220b1..5920480dd 100644 --- a/src/core/acpimac.c +++ b/src/core/acpimac.c @@ -46,11 +46,79 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** MACA signature */ #define MACA_SIGNATURE ACPI_SIGNATURE ( 'M', 'A', 'C', 'A' ) -/** Maximum number of bytes to skip after AMAC/MACA signature +/** RTMA signature */ +#define RTMA_SIGNATURE ACPI_SIGNATURE ( 'R', 'T', 'M', 'A' ) + +/** Maximum number of bytes to skip after ACPI signature * * This is entirely empirical. */ -#define AUXMAC_MAX_SKIP 8 +#define ACPIMAC_MAX_SKIP 8 + +/** An ACPI MAC extraction mechanism */ +struct acpimac_extractor { + /** Prefix string */ + const char *prefix; + /** Encoded MAC length */ + size_t len; + /** Decode MAC + * + * @v mac Encoded MAC + * @v hw_addr MAC address to fill in + * @ret rc Return status code + */ + int ( * decode ) ( const char *mac, uint8_t *hw_addr ); +}; + +/** + * Decode Base16-encoded MAC address + * + * @v mac Encoded MAC + * @v hw_addr MAC address to fill in + * @ret rc Return status code + */ +static int acpimac_decode_base16 ( const char *mac, uint8_t *hw_addr ) { + int len; + int rc; + + /* Attempt to base16-decode MAC address */ + len = base16_decode ( mac, hw_addr, ETH_ALEN ); + if ( len < 0 ) { + rc = len; + DBGC ( colour, "ACPI could not decode base16 MAC \"%s\": %s\n", + mac, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Decode raw MAC address + * + * @v mac Encoded MAC + * @v hw_addr MAC address to fill in + * @ret rc Return status code + */ +static int acpimac_decode_raw ( const char *mac, uint8_t *hw_addr ) { + + memcpy ( hw_addr, mac, ETH_ALEN ); + return 0; +} + +/** "_AUXMAC_" extraction mechanism */ +static struct acpimac_extractor acpimac_auxmac = { + .prefix = "_AUXMAC_#", + .len = ( ETH_ALEN * 2 ), + .decode = acpimac_decode_base16, +}; + +/** "_RTXMAC_" extraction mechanism */ +static struct acpimac_extractor acpimac_rtxmac = { + .prefix = "_RTXMAC_#", + .len = ETH_ALEN, + .decode = acpimac_decode_raw, +}; /** * Extract MAC address from DSDT/SSDT @@ -59,6 +127,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * @v len Length of DSDT/SSDT * @v offset Offset of signature within DSDT/SSDT * @v data Data buffer + * @v extractor ACPI MAC address extractor * @ret rc Return status code * * Some vendors provide a "system MAC address" within the DSDT/SSDT, @@ -72,51 +141,44 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * string that appears shortly after an "AMAC" or "MACA" signature. * This should work for most implementations encountered in practice. */ -static int acpi_extract_mac ( userptr_t zsdt, size_t len, size_t offset, - void *data ) { - static const char prefix[9] = "_AUXMAC_#"; +static int acpimac_extract ( userptr_t zsdt, size_t len, size_t offset, + void *data, struct acpimac_extractor *extractor ){ + size_t prefix_len = strlen ( extractor->prefix ); uint8_t *hw_addr = data; size_t skip = 0; - char auxmac[ sizeof ( prefix ) /* "_AUXMAC_#" */ + - ( ETH_ALEN * 2 ) /* MAC */ + 1 /* "#" */ + 1 /* NUL */ ]; - char *mac = &auxmac[ sizeof ( prefix ) ]; - int decoded_len; + char buf[ prefix_len + extractor->len + 1 /* "#" */ + 1 /* NUL */ ]; + char *mac = &buf[prefix_len]; int rc; /* Skip signature and at least one tag byte */ offset += ( 4 /* signature */ + 1 /* tag byte */ ); - /* Scan for "_AUXMAC_#.....#" close to signature */ + /* Scan for suitable string close to signature */ for ( skip = 0 ; - ( ( skip < AUXMAC_MAX_SKIP ) && - ( offset + skip + sizeof ( auxmac ) ) < len ) ; + ( ( skip < ACPIMAC_MAX_SKIP ) && + ( offset + skip + sizeof ( buf ) ) <= len ) ; skip++ ) { /* Read value */ - copy_from_user ( auxmac, zsdt, ( offset + skip ), - sizeof ( auxmac ) ); + copy_from_user ( buf, zsdt, ( offset + skip ), + sizeof ( buf ) ); /* Check for expected format */ - if ( memcmp ( auxmac, prefix, sizeof ( prefix ) ) != 0 ) + if ( memcmp ( buf, extractor->prefix, prefix_len ) != 0 ) continue; - if ( auxmac[ sizeof ( auxmac ) - 2 ] != '#' ) + if ( buf[ sizeof ( buf ) - 2 ] != '#' ) continue; - if ( auxmac[ sizeof ( auxmac ) - 1 ] != '\0' ) + if ( buf[ sizeof ( buf ) - 1 ] != '\0' ) continue; - DBGC ( colour, "ACPI found MAC string \"%s\"\n", auxmac ); + DBGC ( colour, "ACPI found MAC:\n" ); + DBGC_HDA ( colour, ( offset + skip ), buf, sizeof ( buf ) ); /* Terminate MAC address string */ - mac = &auxmac[ sizeof ( prefix ) ]; - mac[ ETH_ALEN * 2 ] = '\0'; + mac[extractor->len] = '\0'; /* Decode MAC address */ - decoded_len = base16_decode ( mac, hw_addr, ETH_ALEN ); - if ( decoded_len < 0 ) { - rc = decoded_len; - DBGC ( colour, "ACPI could not decode MAC \"%s\": %s\n", - mac, strerror ( rc ) ); + if ( ( rc = extractor->decode ( mac, hw_addr ) ) != 0 ) return rc; - } /* Check MAC address validity */ if ( ! is_valid_ether_addr ( hw_addr ) ) { @@ -131,6 +193,36 @@ static int acpi_extract_mac ( userptr_t zsdt, size_t len, size_t offset, return -ENOENT; } +/** + * Extract "_AUXMAC_" MAC address from DSDT/SSDT + * + * @v zsdt DSDT or SSDT + * @v len Length of DSDT/SSDT + * @v offset Offset of signature within DSDT/SSDT + * @v data Data buffer + * @ret rc Return status code + */ +static int acpimac_extract_auxmac ( userptr_t zsdt, size_t len, size_t offset, + void *data ) { + + return acpimac_extract ( zsdt, len, offset, data, &acpimac_auxmac ); +} + +/** + * Extract "_RTXMAC_" MAC address from DSDT/SSDT + * + * @v zsdt DSDT or SSDT + * @v len Length of DSDT/SSDT + * @v offset Offset of signature within DSDT/SSDT + * @v data Data buffer + * @ret rc Return status code + */ +static int acpimac_extract_rtxmac ( userptr_t zsdt, size_t len, size_t offset, + void *data ) { + + return acpimac_extract ( zsdt, len, offset, data, &acpimac_rtxmac ); +} + /** * Extract MAC address from DSDT/SSDT * @@ -142,12 +234,17 @@ int acpi_mac ( uint8_t *hw_addr ) { /* Look for an "AMAC" address */ if ( ( rc = acpi_extract ( AMAC_SIGNATURE, hw_addr, - acpi_extract_mac ) ) == 0 ) + acpimac_extract_auxmac ) ) == 0 ) return 0; /* Look for a "MACA" address */ if ( ( rc = acpi_extract ( MACA_SIGNATURE, hw_addr, - acpi_extract_mac ) ) == 0 ) + acpimac_extract_auxmac ) ) == 0 ) + return 0; + + /* Look for a "RTMA" address */ + if ( ( rc = acpi_extract ( RTMA_SIGNATURE, hw_addr, + acpimac_extract_rtxmac ) ) == 0 ) return 0; return -ENOENT; diff --git a/src/tests/acpi_test.c b/src/tests/acpi_test.c index 972067ee2..1ca5befaf 100644 --- a/src/tests/acpi_test.c +++ b/src/tests/acpi_test.c @@ -159,6 +159,24 @@ ACPI_TABLES ( maca_tables, &maca_ssdt1, &maca_ssdt2 ); ACPI_MAC ( maca, &maca_tables, DATA ( 0x52, 0x54, 0x00, 0x11, 0x22, 0x33 ) ); +/** "RTMA" SSDT */ +ACPI_TABLE ( rtma_ssdt, "SSDT", + DATA ( 0x53, 0x53, 0x44, 0x54, 0x44, 0x00, 0x00, 0x00, 0x02, + 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x04, 0x06, 0x21, 0x20, + 0x10, 0x1f, 0x5c, 0x5f, 0x53, 0x42, 0x5f, 0x14, 0x18, + 0x52, 0x54, 0x4d, 0x41, 0x08, 0x0d, 0x5f, 0x52, 0x54, + 0x58, 0x4d, 0x41, 0x43, 0x5f, 0x23, 0x52, 0x54, 0x30, + 0x30, 0x30, 0x31, 0x23, 0x00 ) ); + +/** "RTMA" test tables */ +ACPI_TABLES ( rtma_tables, &rtma_ssdt ); + +/** "RTMA" test */ +ACPI_MAC ( rtma, &rtma_tables, + DATA ( 0x52, 0x54, 0x30, 0x30, 0x30, 0x31 ) ); + /** Current ACPI test table set */ static struct acpi_test_tables *acpi_test_tables; @@ -229,6 +247,7 @@ static void acpi_test_exec ( void ) { /* MAC extraction tests */ acpi_mac_ok ( &amac ); acpi_mac_ok ( &maca ); + acpi_mac_ok ( &rtma ); } /** ACPI self-test */ -- cgit v1.2.3-55-g7522