1/* $NetBSD: t_modctl.c,v 1.16 2020/02/22 19:54:34 pgoyette Exp $ */ 2/* 3 * Copyright (c) 2008 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 16 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 22 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 24 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 26 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__KERNEL_RCSID(0, "$NetBSD: t_modctl.c,v 1.16 2020/02/22 19:54:34 pgoyette Exp $"); 31 32#include <sys/module.h> 33#include <sys/sysctl.h> 34 35#include <assert.h> 36#include <errno.h> 37#include <stdarg.h> 38#include <stdbool.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/evcnt.h> 43 44#include <prop/proplib.h> 45 46#include <atf-c.h> 47 48enum presence_check { all_checks, stat_check, sysctl_check, evcnt_check }; 49 50static void check_permission(void); 51static bool get_modstat_info(const char *, modstat_t *); 52static bool get_sysctl(const char *, void *buf, const size_t); 53static bool k_helper_is_present_stat(void); 54static bool k_helper_is_present_sysctl(void); 55static bool k_helper_is_present_evcnt(void); 56static bool k_helper_is_present(enum presence_check); 57static int load(prop_dictionary_t, bool, const char *, ...); 58static int unload(const char *, bool); 59static void unload_cleanup(const char *); 60 61/* --------------------------------------------------------------------- */ 62/* Auxiliary functions */ 63/* --------------------------------------------------------------------- */ 64 65/* 66 * A function checking whether we are allowed to load modules currently 67 * (either the kernel is not modular, or securelevel may prevent it) 68 */ 69static void 70check_permission(void) 71{ 72 int err; 73 74 err = modctl(MODCTL_EXISTS, 0); 75 if (err == 0) return; 76 if (errno == ENOSYS) 77 atf_tc_skip("Kernel does not have 'options MODULAR'."); 78 else if (errno == EPERM) 79 atf_tc_skip("Module loading administratively forbidden"); 80 ATF_REQUIRE_EQ_MSG(errno, 0, "unexpected error %d from " 81 "modctl(MODCTL_EXISTS, 0)", errno); 82} 83 84static bool 85get_modstat_info(const char *name, modstat_t *msdest) 86{ 87 bool found; 88 size_t len; 89 int count; 90 struct iovec iov; 91 modstat_t *ms; 92 modstat_t m; 93 94 check_permission(); 95 for (len = 8192; ;) { 96 iov.iov_base = malloc(len); 97 iov.iov_len = len; 98 99 errno = 0; 100 101 if (modctl(MODCTL_STAT, &iov) != 0) { 102 int err = errno; 103 fprintf(stderr, "modctl(MODCTL_STAT) failed: %s\n", 104 strerror(err)); 105 atf_tc_fail("Failed to query module status"); 106 } 107 if (len >= iov.iov_len) 108 break; 109 free(iov.iov_base); 110 len = iov.iov_len; 111 } 112 113 found = false; 114 count = *(int *)iov.iov_base; 115 ms = (modstat_t *)((char *)iov.iov_base + sizeof(int)); 116 while ( count ) { 117 memcpy(&m, ms, sizeof(m)); 118 if (strcmp(m.ms_name, name) == 0) { 119 if (msdest != NULL) 120 memcpy(msdest, &m, sizeof(*msdest)); 121 found = true; 122 break; 123 } 124 ms++; 125 count--; 126 } 127 128 free(iov.iov_base); 129 130 return found; 131} 132 133/* 134 * Queries a sysctl property. 135 */ 136static bool 137get_sysctl(const char *name, void *buf, const size_t len) 138{ 139 size_t len2 = len; 140 printf("Querying sysctl variable: %s\n", name); 141 int ret = sysctlbyname(name, buf, &len2, NULL, 0); 142 if (ret == -1 && errno != ENOENT) { 143 fprintf(stderr, "sysctlbyname(2) failed: %s\n", 144 strerror(errno)); 145 atf_tc_fail("Failed to query %s", name); 146 } 147 return ret != -1; 148} 149 150/* 151 * Returns a boolean indicating if the k_helper module was loaded 152 * successfully. This implementation uses modctl(2)'s MODCTL_STAT 153 * subcommand to do the check. 154 */ 155static bool 156k_helper_is_present_stat(void) 157{ 158 159 return get_modstat_info("k_helper", NULL); 160} 161 162/* 163 * Returns a boolean indicating if the k_helper module was loaded 164 * successfully. This implementation uses the module's sysctl 165 * installed node to do the check. 166 */ 167static bool 168k_helper_is_present_sysctl(void) 169{ 170 size_t present; 171 172 return get_sysctl("vendor.k_helper.present", &present, 173 sizeof(present)); 174} 175 176/* 177 * Returns a boolean indicating if the k_helper module was loaded 178 * successfully. This implementation uses the module's evcnt 179 * to do the check. 180 */ 181static bool 182k_helper_is_present_evcnt(void) 183{ 184 const int mib[4] = {CTL_KERN, KERN_EVCNT, EVCNT_TYPE_ANY, 185 KERN_EVCNT_COUNT_ANY }; 186 int error; 187 size_t newlen, buflen = 0; 188 void *buf0, *buf = NULL; 189 const struct evcnt_sysctl *evs, *last_evs; 190 191 for (;;) { 192 if (buflen) 193 buf = malloc(buflen); 194 error = sysctl(mib, __arraycount(mib), buf, &newlen, NULL, 0); 195 if (error) { 196 if (buf) 197 free(buf); 198 return false; 199 } 200 if (newlen <= buflen) { 201 buflen = newlen; 202 break; 203 } 204 if (buf) 205 free(buf); 206 buflen = newlen; 207 } 208 evs = buf0 = buf; 209 last_evs = (void *)((char *)buf + buflen); 210 buflen /= sizeof(uint64_t); 211 while (evs < last_evs 212 && buflen >= sizeof(*evs)/sizeof(uint64_t) 213 && buflen >= evs->ev_len) { 214 if ( strncmp(evs->ev_strings, "k_helper", evs->ev_grouplen) 215 == 0) { 216 free(buf); 217 return true; 218 } 219 buflen -= evs->ev_len; 220 evs = (const void *)((const uint64_t *)evs + evs->ev_len); 221 } 222 free(buf); 223 return false; 224} 225 226/* 227 * Returns a boolean indicating if the k_helper module was loaded 228 * successfully. The 'how' parameter specifies the implementation to 229 * use to do the check. 230 */ 231static bool 232k_helper_is_present(enum presence_check how) 233{ 234 bool found; 235 236 switch (how) { 237 case all_checks: 238 found = k_helper_is_present_stat(); 239 ATF_CHECK(k_helper_is_present_sysctl() == found); 240 ATF_CHECK(k_helper_is_present_evcnt() == found); 241 break; 242 243 case stat_check: 244 found = k_helper_is_present_stat(); 245 break; 246 247 case sysctl_check: 248 found = k_helper_is_present_sysctl(); 249 break; 250 251 case evcnt_check: 252 found = k_helper_is_present_evcnt(); 253 break; 254 255 default: 256 found = false; 257 assert(found); 258 } 259 260 return found; 261} 262 263/* 264 * Loads the specified module from a file. If fatal is set and an error 265 * occurs when loading the module, an error message is printed and the 266 * test case is aborted. 267 */ 268static __printflike(3, 4) int 269load(prop_dictionary_t props, bool fatal, const char *fmt, ...) 270{ 271 int err; 272 va_list ap; 273 char filename[MAXPATHLEN], *propsstr; 274 modctl_load_t ml; 275 276 check_permission(); 277 if (props == NULL) { 278 props = prop_dictionary_create(); 279 propsstr = prop_dictionary_externalize(props); 280 ATF_CHECK(propsstr != NULL); 281 prop_object_release(props); 282 } else { 283 propsstr = prop_dictionary_externalize(props); 284 ATF_CHECK(propsstr != NULL); 285 } 286 287 va_start(ap, fmt); 288 vsnprintf(filename, sizeof(filename), fmt, ap); 289 va_end(ap); 290 291 ml.ml_filename = filename; 292 ml.ml_flags = 0; 293 ml.ml_props = propsstr; 294 ml.ml_propslen = strlen(propsstr); 295 296 printf("Loading module %s\n", filename); 297 errno = err = 0; 298 299 if (modctl(MODCTL_LOAD, &ml) == -1) { 300 err = errno; 301 fprintf(stderr, "modctl(MODCTL_LOAD, %s), failed: %s\n", 302 filename, strerror(err)); 303 if (fatal) 304 atf_tc_fail("Module load failed"); 305 } 306 307 free(propsstr); 308 309 return err; 310} 311 312/* 313 * Unloads the specified module. If silent is true, nothing will be 314 * printed and no errors will be raised if the unload was unsuccessful. 315 */ 316static int 317unload(const char *name, bool fatal) 318{ 319 int err; 320 321 check_permission(); 322 printf("Unloading module %s\n", name); 323 errno = err = 0; 324 325 if (modctl(MODCTL_UNLOAD, __UNCONST(name)) == -1) { 326 err = errno; 327 fprintf(stderr, "modctl(MODCTL_UNLOAD, %s) failed: %s\n", 328 name, strerror(err)); 329 if (fatal) 330 atf_tc_fail("Module unload failed"); 331 } 332 return err; 333} 334 335/* 336 * A silent version of unload, to be called as part of the cleanup 337 * process only. 338 */ 339static void 340unload_cleanup(const char *name) 341{ 342 343 (void)modctl(MODCTL_UNLOAD, __UNCONST(name)); 344} 345 346/* --------------------------------------------------------------------- */ 347/* Test cases */ 348/* --------------------------------------------------------------------- */ 349 350ATF_TC_WITH_CLEANUP(cmd_load); 351ATF_TC_HEAD(cmd_load, tc) 352{ 353 atf_tc_set_md_var(tc, "descr", "Tests for the MODCTL_LOAD command"); 354 atf_tc_set_md_var(tc, "require.user", "root"); 355} 356ATF_TC_BODY(cmd_load, tc) 357{ 358 char longname[MAXPATHLEN]; 359 size_t i; 360 361 ATF_CHECK(load(NULL, false, " ") == ENOENT); 362 ATF_CHECK(load(NULL, false, "non-existent.o") == ENOENT); 363 364 for (i = 0; i < MAXPATHLEN - 1; i++) 365 longname[i] = 'a'; 366 longname[MAXPATHLEN - 1] = '\0'; 367 ATF_CHECK(load(NULL, false, "%s", longname) == ENAMETOOLONG); 368 369 ATF_CHECK(!k_helper_is_present(stat_check)); 370 load(NULL, true, "%s/k_helper/k_helper.kmod", 371 atf_tc_get_config_var(tc, "srcdir")); 372 printf("Checking if load was successful\n"); 373 ATF_CHECK(k_helper_is_present(stat_check)); 374} 375ATF_TC_CLEANUP(cmd_load, tc) 376{ 377 unload_cleanup("k_helper"); 378} 379 380ATF_TC_WITH_CLEANUP(cmd_load_props); 381ATF_TC_HEAD(cmd_load_props, tc) 382{ 383 atf_tc_set_md_var(tc, "descr", "Tests for the MODCTL_LOAD command, " 384 "providing extra load-time properties"); 385 atf_tc_set_md_var(tc, "require.user", "root"); 386} 387ATF_TC_BODY(cmd_load_props, tc) 388{ 389 prop_dictionary_t props; 390 391 printf("Loading module without properties\n"); 392 props = prop_dictionary_create(); 393 load(props, true, "%s/k_helper/k_helper.kmod", 394 atf_tc_get_config_var(tc, "srcdir")); 395 prop_object_release(props); 396 { 397 int ok; 398 ATF_CHECK(get_sysctl("vendor.k_helper.prop_str_ok", 399 &ok, sizeof(ok))); 400 ATF_CHECK(!ok); 401 } 402 unload("k_helper", true); 403 404 printf("Loading module with a string property\n"); 405 props = prop_dictionary_create(); 406 prop_dictionary_set(props, "prop_str", 407 prop_string_create_cstring("1st string")); 408 load(props, true, "%s/k_helper/k_helper.kmod", 409 atf_tc_get_config_var(tc, "srcdir")); 410 prop_object_release(props); 411 { 412 int ok; 413 ATF_CHECK(get_sysctl("vendor.k_helper.prop_str_ok", 414 &ok, sizeof(ok))); 415 ATF_CHECK(ok); 416 417 char val[128]; 418 ATF_CHECK(get_sysctl("vendor.k_helper.prop_str_val", 419 &val, sizeof(val))); 420 ATF_CHECK(strcmp(val, "1st string") == 0); 421 } 422 unload("k_helper", true); 423 424 printf("Loading module with a different string property\n"); 425 props = prop_dictionary_create(); 426 prop_dictionary_set(props, "prop_str", 427 prop_string_create_cstring("2nd string")); 428 load(props, true, "%s/k_helper/k_helper.kmod", 429 atf_tc_get_config_var(tc, "srcdir")); 430 prop_object_release(props); 431 { 432 int ok; 433 ATF_CHECK(get_sysctl("vendor.k_helper.prop_str_ok", 434 &ok, sizeof(ok))); 435 ATF_CHECK(ok); 436 437 char val[128]; 438 ATF_CHECK(get_sysctl("vendor.k_helper.prop_str_val", 439 &val, sizeof(val))); 440 ATF_CHECK(strcmp(val, "2nd string") == 0); 441 } 442 unload("k_helper", true); 443} 444ATF_TC_CLEANUP(cmd_load_props, tc) 445{ 446 unload_cleanup("k_helper"); 447} 448 449ATF_TC_WITH_CLEANUP(cmd_load_recurse); 450ATF_TC_HEAD(cmd_load_recurse, tc) 451{ 452 atf_tc_set_md_var(tc, "descr", "Tests for the MODCTL_LOAD command, " 453 "with recursive module_load()"); 454 atf_tc_set_md_var(tc, "require.user", "root"); 455} 456ATF_TC_BODY(cmd_load_recurse, tc) 457{ 458 prop_dictionary_t props; 459 char filename[MAXPATHLEN]; 460 461 printf("Loading module with request to load another module\n"); 462 props = prop_dictionary_create(); 463 snprintf(filename, sizeof(filename), "%s/k_helper2/k_helper2.kmod", 464 atf_tc_get_config_var(tc, "srcdir")); 465 prop_dictionary_set(props, "prop_recurse", 466 prop_string_create_cstring(filename)); 467 load(props, true, "%s/k_helper/k_helper.kmod", 468 atf_tc_get_config_var(tc, "srcdir")); 469 { 470 int ok; 471 ATF_CHECK(get_sysctl("vendor.k_helper.prop_int_load", 472 &ok, sizeof(ok))); 473 ATF_CHECK(ok == 0); 474 ATF_CHECK(get_sysctl("vendor.k_helper2.present", 475 &ok, sizeof(ok))); 476 ATF_CHECK(ok); 477 } 478 unload("k_helper", true); 479 unload("k_helper2", true); 480} 481ATF_TC_CLEANUP(cmd_load_recurse, tc) 482{ 483 unload_cleanup("k_helper"); 484 unload_cleanup("k_helper2"); 485} 486 487ATF_TC_WITH_CLEANUP(cmd_stat); 488ATF_TC_HEAD(cmd_stat, tc) 489{ 490 atf_tc_set_md_var(tc, "descr", "Tests for the MODCTL_STAT command"); 491 atf_tc_set_md_var(tc, "require.user", "root"); 492} 493ATF_TC_BODY(cmd_stat, tc) 494{ 495 ATF_CHECK(!k_helper_is_present(all_checks)); 496 497 load(NULL, true, "%s/k_helper/k_helper.kmod", 498 atf_tc_get_config_var(tc, "srcdir")); 499 ATF_CHECK(k_helper_is_present(all_checks)); 500 { 501 modstat_t ms; 502 ATF_CHECK(get_modstat_info("k_helper", &ms)); 503 504 ATF_CHECK(ms.ms_class == MODULE_CLASS_MISC); 505 ATF_CHECK(ms.ms_source == MODULE_SOURCE_FILESYS); 506 ATF_CHECK(ms.ms_refcnt == 0); 507 } 508 unload("k_helper", true); 509 510 ATF_CHECK(!k_helper_is_present(all_checks)); 511} 512ATF_TC_CLEANUP(cmd_stat, tc) 513{ 514 unload_cleanup("k_helper"); 515} 516 517ATF_TC_WITH_CLEANUP(cmd_unload); 518ATF_TC_HEAD(cmd_unload, tc) 519{ 520 atf_tc_set_md_var(tc, "descr", "Tests for the MODCTL_UNLOAD command"); 521 atf_tc_set_md_var(tc, "require.user", "root"); 522} 523ATF_TC_BODY(cmd_unload, tc) 524{ 525 load(NULL, true, "%s/k_helper/k_helper.kmod", 526 atf_tc_get_config_var(tc, "srcdir")); 527 528 ATF_CHECK(unload("", false) == ENOENT); 529 ATF_CHECK(unload("non-existent.kmod", false) == ENOENT); 530 ATF_CHECK(unload("k_helper.kmod", false) == ENOENT); 531 532 ATF_CHECK(k_helper_is_present(stat_check)); 533 unload("k_helper", true); 534 printf("Checking if unload was successful\n"); 535 ATF_CHECK(!k_helper_is_present(stat_check)); 536} 537ATF_TC_CLEANUP(cmd_unload, tc) 538{ 539 unload_cleanup("k_helper"); 540} 541 542/* --------------------------------------------------------------------- */ 543/* Main */ 544/* --------------------------------------------------------------------- */ 545 546ATF_TP_ADD_TCS(tp) 547{ 548 549 ATF_TP_ADD_TC(tp, cmd_load); 550 ATF_TP_ADD_TC(tp, cmd_load_props); 551 ATF_TP_ADD_TC(tp, cmd_stat); 552 ATF_TP_ADD_TC(tp, cmd_load_recurse); 553 ATF_TP_ADD_TC(tp, cmd_unload); 554 555 return atf_no_error(); 556} 557