/*- * Copyright (C) 2025 ConnectWise, LLC. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define UEXTERR_CATEGORY "tests/sys/kern/exterr_test.c" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "freebsd_test_suite/macros.h" /* * Pin the message format by clearing EXTERROR_VERBOSE, which would * otherwise change the output shape. */ static void pin_exterror_format(void) { unsetenv("EXTERROR_VERBOSE"); } ATF_TC(gettext_extended); ATF_TC_HEAD(gettext_extended, tc) { atf_tc_set_md_var(tc, "descr", "Retrieve an extended error message"); } ATF_TC_BODY(gettext_extended, tc) { char exterr[UEXTERROR_MAXLEN]; int r; pin_exterror_format(); /* * Use an invalid call to mmap() because it supports extended error * messages, requires no special resources, and does not need root. */ ATF_CHECK_ERRNO(ENOTSUP, mmap(NULL, 0, PROT_MAX(PROT_READ) | PROT_WRITE, 0, -1, 0)); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); printf("Extended error: %s\n", exterr); ATF_REQUIRE_FEATURE("exterr_strings"); /* Note: error string may need to be updated due to kernel changes */ ATF_CHECK(strstr(exterr, " is not subset of ") != NULL); } ATF_TC(uexterr_set); ATF_TC_HEAD(uexterr_set, tc) { atf_tc_set_md_var(tc, "descr", "Set extended error message from userspace"); } ATF_TC_BODY(uexterr_set, tc) { char exterr[UEXTERROR_MAXLEN]; int r; errno = 0; UEXTERROR(EINVAL, "pointer %p int %d", (uintptr_t)&r, UINTMAX_MAX); ATF_CHECK_EQ(errno, EINVAL); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); printf("Extended error: %s\n", exterr); /* The pointer's value will change, but the rest should be stable. */ ATF_CHECK(strncmp("pointer 0x", exterr, strlen("pointer 0x")) == 0); ATF_CHECK(strstr(exterr, " int -1") != 0); errno = 0; UEXTERROR(EINVAL, "long 0x%lx int %u", UINTMAX_MAX, UINTMAX_MAX); ATF_CHECK_EQ(errno, EINVAL); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ("long 0xffffffffffffffff int 4294967295", exterr); } static void check_bad_fmt(const char *fmt) { char exterr[UEXTERROR_MAXLEN]; char expected[UEXTERROR_MAXLEN]; int r; sprintf(expected, ":%s", fmt); errno = 0; UEXTERROR(EINVAL, fmt, 0xAAAAAAAAAAAAAAAA); ATF_CHECK_EQ(errno, EINVAL); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ(expected, exterr); } ATF_TC(uexterr_bad_fmt); ATF_TC_HEAD(uexterr_bad_fmt, tc) { atf_tc_set_md_var(tc, "descr", "Test invalid or unsupported formats"); } ATF_TC_BODY(uexterr_bad_fmt, tc) { char exterr[UEXTERROR_MAXLEN]; int r; int target = 0; /* Write gadget */ errno = 0; UEXTERROR(EINVAL, "write to %n", (uintptr_t)&target); ATF_CHECK_EQ_MSG(0, target, "Write via %%n happened"); ATF_CHECK_EQ(errno, EINVAL); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ("write to :%n", exterr); /* Too long */ errno = 0; UEXTERROR(EINVAL, "%llllllllllllllllllllllllllllllllllllllllllllllld", 0xAAAAAAAAAAAAAAAA); ATF_CHECK_EQ(errno, EINVAL); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ("", exterr); /* Incomplete */ check_bad_fmt("%"); check_bad_fmt("%l"); /* Obsolete or nonsensical */ check_bad_fmt("%D"); check_bad_fmt("%O"); check_bad_fmt("%U"); check_bad_fmt("%f"); check_bad_fmt("%F"); check_bad_fmt("%g"); check_bad_fmt("%G"); check_bad_fmt("%a"); check_bad_fmt("%A"); check_bad_fmt("%S"); check_bad_fmt("%s"); check_bad_fmt("%m"); } ATF_TC(gettext_noextended); ATF_TC_HEAD(gettext_noextended, tc) { atf_tc_set_md_var(tc, "descr", "Fail to retrieve an extended error message because none exists"); } ATF_TC_BODY(gettext_noextended, tc) { char exterr[UEXTERROR_MAXLEN]; int r; pin_exterror_format(); ATF_CHECK_ERRNO(EINVAL, exterrctl(EXTERRCTL_UD, 0, NULL)); r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ(exterr, ""); } ATF_TC(gettext_noextended_after_extended); ATF_TC_HEAD(gettext_noextended_after_extended, tc) { atf_tc_set_md_var(tc, "descr", "uexterr_gettext should not return a stale extended error message"); } ATF_TC_BODY(gettext_noextended_after_extended, tc) { char exterr[UEXTERROR_MAXLEN]; int r; pin_exterror_format(); /* * First do something that will create an extended error message, but * ignore it. */ ATF_CHECK_ERRNO(ENOTSUP, mmap(NULL, 0, PROT_MAX(PROT_READ) | PROT_WRITE, 0, -1, 0)); /* Then do something that won't create an extended error message */ ATF_CHECK_ERRNO(EINVAL, exterrctl(EXTERRCTL_UD, 0, NULL)); /* Hopefully we won't see the stale extended error message */ r = uexterr_gettext(exterr, sizeof(exterr)); ATF_CHECK_EQ(0, r); ATF_CHECK_STREQ(exterr, ""); } ATF_TC(exterr_dynamic_categories); ATF_TC_HEAD(exterr_dynamic_categories, tc) { atf_tc_set_md_var(tc, "descr", "directly check there is at least one registered category"); } ATF_TC_BODY(exterr_dynamic_categories, tc) { int mib[4]; size_t len; char filename_buf[128]; len = nitems(mib); ATF_REQUIRE_EQ(sysctlnametomib("kern.exterr.categories", mib, &len), 0); mib[3] = 1; len = sizeof(filename_buf); ATF_REQUIRE_EQ(sysctl(mib, nitems(mib), filename_buf, &len, NULL, 0), 0); printf("%s\n", filename_buf); /* We can't know what it is, but make sure it's non-empty */ ATF_REQUIRE(strlen(filename_buf) > 1); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, gettext_extended); ATF_TP_ADD_TC(tp, uexterr_set); ATF_TP_ADD_TC(tp, uexterr_bad_fmt); ATF_TP_ADD_TC(tp, gettext_noextended); ATF_TP_ADD_TC(tp, gettext_noextended_after_extended); ATF_TP_ADD_TC(tp, exterr_dynamic_categories); return (atf_no_error()); }