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