fdt_string() is used to retrieve strings from a DT blob's strings section. It's rarely used directly, but is widely used internally. However, it doesn't do any bounds checking, which means in the case of a corrupted blob it could access bad memory, which libfdt is supposed to avoid. This write a safe alternative to fdt_string, fdt_get_string(). It checks both that the given offset is within the string section and that the string it points to is properly \0 terminated within the section. It also returns the string's length as a convenience (since it needs to determine to do the checks anyway). fdt_string() is rewritten in terms of fdt_get_string() for compatibility. Most of the diff here is actually testing infrastructure. Signed-off-by: David Gibson <david@xxxxxxxxxxxxxxxxxxxxx> --- libfdt/fdt_ro.c | 61 +++++++++++++++++++++++++++++++++++-- libfdt/libfdt.h | 18 ++++++++++- libfdt/version.lds | 2 +- tests/.gitignore | 1 + tests/Makefile.tests | 2 +- tests/run_tests.sh | 1 + tests/testdata.h | 1 + tests/testutils.c | 11 +++++-- tests/trees.S | 26 ++++++++++++++++ tests/truncated_string.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 tests/truncated_string.c diff --git a/libfdt/fdt_ro.c b/libfdt/fdt_ro.c index 4f4ef44..347aa7b 100644 --- a/libfdt/fdt_ro.c +++ b/libfdt/fdt_ro.c @@ -76,17 +76,72 @@ static int fdt_nodename_eq_(const void *fdt, int offset, return 0; } +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) +{ + uint32_t absoffset = stroffset + fdt_off_dt_strings(fdt); + size_t len; + int err; + const char *s, *n; + + err = fdt_ro_probe_(fdt); + if (err != 0) + goto fail; + + err = -FDT_ERR_BADOFFSET; + if (absoffset >= fdt_totalsize(fdt)) + goto fail; + len = fdt_totalsize(fdt) - absoffset; + + if (fdt_magic(fdt) == FDT_MAGIC) { + if (stroffset < 0) + goto fail; + if (fdt_version(fdt) >= 17) { + if (stroffset >= fdt_size_dt_strings(fdt)) + goto fail; + if ((fdt_size_dt_strings(fdt) - stroffset) < len) + len = fdt_size_dt_strings(fdt) - stroffset; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + if ((stroffset >= 0) + || (stroffset < -fdt_size_dt_strings(fdt))) + goto fail; + if ((-stroffset) < len) + len = -stroffset; + } else { + err = -FDT_ERR_INTERNAL; + goto fail; + } + + s = (const char *)fdt + absoffset; + n = memchr(s, '\0', len); + if (!n) { + /* missing terminating NULL */ + err = -FDT_ERR_TRUNCATED; + goto fail; + } + + if (lenp) + *lenp = n - s; + return s; + +fail: + if (lenp) + *lenp = err; + return NULL; +} + const char *fdt_string(const void *fdt, int stroffset) { - return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; + return fdt_get_string(fdt, stroffset, NULL); } static int fdt_string_eq_(const void *fdt, int stroffset, const char *s, int len) { - const char *p = fdt_string(fdt, stroffset); + int slen; + const char *p = fdt_get_string(fdt, stroffset, &slen); - return (strlen(p) == len) && (memcmp(p, s, len) == 0); + return p && (slen == len) && (memcmp(p, s, len) == 0); } uint32_t fdt_get_max_phandle(const void *fdt) diff --git a/libfdt/libfdt.h b/libfdt/libfdt.h index 1032d38..2c40bd8 100644 --- a/libfdt/libfdt.h +++ b/libfdt/libfdt.h @@ -285,6 +285,22 @@ int fdt_move(const void *fdt, void *buf, int bufsize); /* Read-only functions */ /**********************************************************************/ +/** + * fdt_get_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * @lenp: optional pointer to return the string's length + * + * fdt_get_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt, and optionally also + * returns the string's length in *lenp. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); + /** * fdt_string - retrieve a string from the strings block of a device tree * @fdt: pointer to the device tree blob @@ -295,7 +311,7 @@ int fdt_move(const void *fdt, void *buf, int bufsize); * * returns: * a pointer to the string, on success - * NULL, if stroffset is out of bounds + * NULL, if stroffset is out of bounds, or doesn't point to a valid string */ const char *fdt_string(const void *fdt, int stroffset); diff --git a/libfdt/version.lds b/libfdt/version.lds index 18fb69f..9f5d708 100644 --- a/libfdt/version.lds +++ b/libfdt/version.lds @@ -65,7 +65,7 @@ LIBFDT_1.2 { fdt_stringlist_get; fdt_resize; fdt_overlay_apply; - + fdt_get_string; local: *; }; diff --git a/tests/.gitignore b/tests/.gitignore index 1c25219..f643cf9 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -60,5 +60,6 @@ tmp.* /supernode_atdepth_offset /sw_tree1 /truncated_property +/truncated_string /utilfdt_test /value-labels diff --git a/tests/Makefile.tests b/tests/Makefile.tests index 1f0ba99..7c04ff9 100644 --- a/tests/Makefile.tests +++ b/tests/Makefile.tests @@ -29,7 +29,7 @@ LIB_TESTS_L = get_mem_rsv \ check_path check_header LIB_TESTS = $(LIB_TESTS_L:%=$(TESTS_PREFIX)%) -LIBTREE_TESTS_L = truncated_property +LIBTREE_TESTS_L = truncated_property truncated_string LIBTREE_TESTS = $(LIBTREE_TESTS_L:%=$(TESTS_PREFIX)%) DL_LIB_TESTS_L = asm_tree_dump value-labels diff --git a/tests/run_tests.sh b/tests/run_tests.sh index f2a0198..8938d8b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -414,6 +414,7 @@ libfdt_tests () { # Tests for behaviour on various sorts of corrupted trees run_test truncated_property + run_test truncated_string # Check aliases support in fdt_path_offset run_dtc_test -I dts -O dtb -o aliases.dtb aliases.dts diff --git a/tests/testdata.h b/tests/testdata.h index c30f0c8..b257011 100644 --- a/tests/testdata.h +++ b/tests/testdata.h @@ -47,4 +47,5 @@ extern struct fdt_header bad_node_char; extern struct fdt_header bad_node_format; extern struct fdt_header bad_prop_char; extern struct fdt_header ovf_size_strings; +extern struct fdt_header truncated_string; #endif /* ! __ASSEMBLY */ diff --git a/tests/testutils.c b/tests/testutils.c index 521f4f1..f03140e 100644 --- a/tests/testutils.c +++ b/tests/testutils.c @@ -88,7 +88,7 @@ void check_property(void *fdt, int nodeoffset, const char *name, int len, const void *val) { const struct fdt_property *prop; - int retlen; + int retlen, namelen; uint32_t tag, nameoff, proplen; const char *propname; @@ -106,8 +106,13 @@ void check_property(void *fdt, int nodeoffset, const char *name, if (tag != FDT_PROP) FAIL("Incorrect tag 0x%08x on property \"%s\"", tag, name); - propname = fdt_string(fdt, nameoff); - if (!propname || !streq(propname, name)) + propname = fdt_get_string(fdt, nameoff, &namelen); + if (!propname) + FAIL("Couldn't get property name: %s", fdt_strerror(namelen)); + if (namelen != strlen(propname)) + FAIL("Incorrect prop name length: %d instead of %zd", + namelen, strlen(propname)); + if (!streq(propname, name)) FAIL("Property name mismatch \"%s\" instead of \"%s\"", propname, name); if (proplen != retlen) diff --git a/tests/trees.S b/tests/trees.S index 6898cf9..7ae1bde 100644 --- a/tests/trees.S +++ b/tests/trees.S @@ -39,6 +39,9 @@ tree##_rsvmap_end: ; FDTLONG(len) ; \ FDTLONG(tree##_##name - tree##_strings) ; +#define PROP_EMPTY(tree, name) \ + PROPHDR(tree, name, 0) ; + #define PROP_INT(tree, name, val) \ PROPHDR(tree, name, 4) \ FDTLONG(val) ; @@ -233,3 +236,26 @@ ovf_size_strings_strings: ovf_size_strings_bad_string = ovf_size_strings_strings + 0x10000000 ovf_size_strings_strings_end: ovf_size_strings_end: + + + /* truncated_string */ + TREE_HDR(truncated_string) + EMPTY_RSVMAP(truncated_string) + +truncated_string_struct: + BEGIN_NODE("") + PROP_EMPTY(truncated_string, good_string) + PROP_EMPTY(truncated_string, bad_string) + END_NODE + FDTLONG(FDT_END) +truncated_string_struct_end: + +truncated_string_strings: + STRING(truncated_string, good_string, "good") +truncated_string_bad_string: + .byte 'b' + .byte 'a' + .byte 'd' + /* NOTE: terminating \0 deliberately missing */ +truncated_string_strings_end: +truncated_string_end: diff --git a/tests/truncated_string.c b/tests/truncated_string.c new file mode 100644 index 0000000..63214d2 --- /dev/null +++ b/tests/truncated_string.c @@ -0,0 +1,79 @@ +/* + * libfdt - Flat Device Tree manipulation + * Testcase for misbehaviour on a truncated string + * Copyright (C) 2018 David Gibson, IBM Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> + +#include <libfdt.h> + +#include "tests.h" +#include "testdata.h" + +int main(int argc, char *argv[]) +{ + void *fdt = &truncated_string; + const struct fdt_property *good, *bad; + int off, len; + const char *name; + + test_init(argc, argv); + + off = fdt_first_property_offset(fdt, 0); + good = fdt_get_property_by_offset(fdt, off, NULL); + + off = fdt_next_property_offset(fdt, off); + bad = fdt_get_property_by_offset(fdt, off, NULL); + + if (fdt32_to_cpu(good->len) != 0) + FAIL("Unexpected length for good property"); + name = fdt_get_string(fdt, fdt32_to_cpu(good->nameoff), &len); + if (!name) + FAIL("fdt_get_string() failed on good property: %s", + fdt_strerror(len)); + if (len != 4) + FAIL("fdt_get_string() returned length %d (not 4) on good property", + len); + if (!streq(name, "good")) + FAIL("fdt_get_string() returned \"%s\" (not \"good\") on good property", + name); + + if (fdt32_to_cpu(bad->len) != 0) + FAIL("Unexpected length for bad property\n"); + name = fdt_get_string(fdt, fdt32_to_cpu(bad->nameoff), &len); + if (name) + FAIL("fdt_get_string() succeeded incorrectly on bad property"); + else if (len != -FDT_ERR_TRUNCATED) + FAIL("fdt_get_string() gave unexpected error on bad property: %s", + fdt_strerror(len)); + + /* Make sure the 'good' property breaks correctly if we + * truncate the strings section */ + fdt_set_size_dt_strings(fdt, fdt32_to_cpu(good->nameoff) + 4); + name = fdt_get_string(fdt, fdt32_to_cpu(good->nameoff), &len); + if (name) + FAIL("fdt_get_string() succeeded incorrectly on mangled property"); + else if (len != -FDT_ERR_TRUNCATED) + FAIL("fdt_get_string() gave unexpected error on mangled property: %s", + fdt_strerror(len)); + + PASS(); +} -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe devicetree-compiler" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html