1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2023 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 *
12 *
13 * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
14 */
15 #ifdef HAVE_LIBSELINUX
16 #include <selinux/selinux.h>
17 #include <selinux/context.h>
18
19 #include "mountP.h"
20 #include "fileutils.h"
21 #include "linux_version.h"
22
23 static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
24 {
25 void *data = NULL;
26
27 DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
28
29 /* remove all our hooks and free hook data */
30 while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) {
31 if (data)
32 free(data);
33 data = NULL;
34 }
35
36 return 0;
37 }
38
39 static inline int is_option(const char *name, const char *const *names)
40 {
41 const char *const *p;
42
43 for (p = names; p && *p; p++) {
44 if (strcmp(name, *p) == 0)
45 return 1;
46 }
47 return 0;
48 }
49
50 /* Converts rootcontext=@target to the real selinxu context
51 */
52 static int hook_selinux_target(
53 struct libmnt_context *cxt,
54 const struct libmnt_hookset *hs,
55 void *data __attribute__((__unused__)))
56 {
57 struct libmnt_optlist *ol;
58 struct libmnt_opt *opt;
59 const char *tgt, *val;
60 char *raw = NULL;
61 int rc = 0;
62
63 assert(cxt);
64
65 tgt = mnt_fs_get_target(cxt->fs);
66 if (!tgt)
67 return 0;
68 if (cxt->action != MNT_ACT_MOUNT)
69 return 0;
70 ol = mnt_context_get_optlist(cxt);
71 if (!ol)
72 return -EINVAL;
73
74 opt = mnt_optlist_get_named(ol, "rootcontext", NULL);
75 if (!opt)
76 return 0;
77
78 val = mnt_opt_get_value(opt);
79 if (!val || strcmp(val, "@target") != 0)
80 return 0;
81
82
83 rc = getfilecon_raw(tgt, &raw);
84 if (rc <= 0 || !raw) {
85 rc = errno ? -errno : -EINVAL;
86 DBG(HOOK, ul_debugobj(hs, " SELinux fix @target failed [rc=%d]", rc));
87 } else {
88 DBG(HOOK, ul_debugobj(hs, " SELinux fix @target to %s", raw));
89 rc = 0; /* getfilecon_raw(3) returns the size of the extended attribute value */
90 }
91 if (!rc)
92 rc = mnt_opt_set_quoted_value(opt, raw);
93 if (raw)
94 freecon(raw);
95
96 return rc != 0 ? -MNT_ERR_MOUNTOPT : 0;
97 }
98
99 static int hook_prepare_options(
100 struct libmnt_context *cxt,
101 const struct libmnt_hookset *hs,
102 void *data __attribute__((__unused__)))
103 {
104 int rc = 0, se_fix = 0, se_rem = 0;
105 struct libmnt_optlist *ol;
106
107 assert(cxt);
108
109 ol = mnt_context_get_optlist(cxt);
110 if (!ol)
111 return -EINVAL;
112
113 if (!is_selinux_enabled())
114 /* Always remove SELinux garbage if SELinux disabled */
115 se_rem = 1;
116 else if (mnt_optlist_is_remount(ol))
117 /*
118 * Linux kernel < 2.6.39 does not support remount operation
119 * with any selinux specific mount options.
120 *
121 * Kernel 2.6.39 commits: ff36fe2c845cab2102e4826c1ffa0a6ebf487c65
122 * 026eb167ae77244458fa4b4b9fc171209c079ba7
123 * fix this odd behavior, so we don't have to care about it in
124 * userspace.
125 */
126 se_rem = get_linux_version() < KERNEL_VERSION(2, 6, 39);
127 else
128 /* For normal mount, contexts are translated */
129 se_fix = 1;
130
131 DBG(HOOK, ul_debugobj(hs, " SELinux fix options"));
132
133 /* Fix SELinux contexts */
134 if (se_rem || se_fix) {
135 static const char *const selinux_options[] = {
136 "context",
137 "fscontext",
138 "defcontext",
139 "rootcontext",
140 "seclabel",
141 NULL
142 };
143 struct libmnt_iter itr;
144 struct libmnt_opt *opt;
145
146 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
147
148 while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
149 const char *opt_name = mnt_opt_get_name(opt);
150
151 if (!is_option(opt_name, selinux_options))
152 continue;
153 if (se_rem)
154 rc = mnt_optlist_remove_opt(ol, opt);
155 else if (se_fix && mnt_opt_has_value(opt)) {
156 const char *val = mnt_opt_get_value(opt);
157 char *raw = NULL;
158
159 /* @target placeholder is replaced later when target
160 * is already avalable. The mountpoint does not have to exist
161 * yet (for example "-o X-mount.mkdir=" or --target-prefix).
162 */
163 if (strcmp(opt_name, "rootcontext") == 0 &&
164 strcmp(val, "@target") == 0) {
165 rc = mnt_context_insert_hook(cxt, "__mkdir",
166 hs, MNT_STAGE_PREP_TARGET, NULL,
167 hook_selinux_target);
168 continue;
169 } else {
170 rc = selinux_trans_to_raw_context(val, &raw);
171 if (rc == -1 || !raw)
172 rc = -EINVAL;
173 }
174 if (!rc) {
175 DBG(HOOK, ul_debugobj(hs, " %s: %s to %s",
176 opt_name, val, raw));
177 rc = mnt_opt_set_quoted_value(opt, raw);
178 }
179 if (raw)
180 freecon(raw);
181
182 /* temporary for broken fsconfig() syscall */
183 cxt->has_selinux_opt = 1;
184 }
185 if (rc)
186 break;
187 }
188 }
189
190 return rc != 0 ? -MNT_ERR_MOUNTOPT : 0;
191 }
192
193 const struct libmnt_hookset hookset_selinux =
194 {
195 .name = "__selinux",
196
197 .firststage = MNT_STAGE_PREP_OPTIONS,
198 .firstcall = hook_prepare_options,
199
200 .deinit = hookset_deinit
201 };
202
203 #endif /* HAVE_LIBSELINUX */