Line data Source code
1 : /*
2 : * Widelinks VFS module. Causes smbd not to see symlinks.
3 : *
4 : * Copyright (C) Jeremy Allison, 2020
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : /*
21 : What does this module do ? It implements the explicitly insecure
22 : "widelinks = yes" functionality that used to be in the core smbd
23 : code.
24 :
25 : Now this is implemented here, the insecure share-escape code that
26 : explicitly allows escape from an exported share path can be removed
27 : from smbd, leaving it a cleaner and more maintainable code base.
28 :
29 : The smbd code can now always return ACCESS_DENIED if a path
30 : leads outside a share.
31 :
32 : How does it do that ? There are 2 features.
33 :
34 : 1). When the upper layer code does a chdir() call to a pathname,
35 : this module stores the requested pathname inside config->cwd.
36 :
37 : When the upper layer code does a getwd() or realpath(), we return
38 : the absolute path of the value stored in config->cwd, *not* the
39 : position on the underlying filesystem.
40 :
41 : This hides symlinks as if the chdir pathname contains a symlink,
42 : normally doing a realpath call on it would return the real
43 : position on the filesystem. For widelinks = yes, this isn't what
44 : you want. You want the position you think is underneath the share
45 : definition - the symlink path you used to go outside the share,
46 : not the contents of the symlink itself.
47 :
48 : That way, the upper layer smbd code can strictly enforce paths
49 : being underneath a share definition without the knowledge that
50 : "widelinks = yes" has moved us outside the share definition.
51 :
52 : 1a). Note that when setting up a share, smbd may make calls such
53 : as realpath and stat/lstat in order to set up the share definition.
54 : These calls are made *before* smbd calls chdir() to move the working
55 : directory below the exported share definition. In order to allow
56 : this, all the vfs_widelinks functions are coded to just pass through
57 : the vfs call to the next module in the chain if (a). The widelinks
58 : module was loaded in error by an administrator and widelinks is
59 : set to "no". This is the:
60 :
61 : if (!config->active) {
62 : Module not active.
63 : SMB_VFS_NEXT_XXXXX(...)
64 : }
65 :
66 : idiom in the vfs functions.
67 :
68 : 1b). If the module was correctly active, but smbd has yet
69 : to call chdir(), then config->cwd == NULL. In that case
70 : the correct action (to match the previous widelinks behavior
71 : in the code inside smbd) is to pass through the vfs call to
72 : the next module in the chain. That way, any symlinks in the
73 : pathname are still exposed to smbd, which will restrict them to
74 : be under the exported share definition. This allows the module
75 : to "fail safe" for any vfs call made when setting up the share
76 : structure definition, rather than fail unsafe by hiding symlinks
77 : before chdir is called. This is the:
78 :
79 : if (config->cwd == NULL) {
80 : XXXXX syscall before chdir - see note 1b above.
81 : return SMB_VFS_NEXT_XXXXX()
82 : }
83 :
84 : idiom in the vfs functions.
85 :
86 : 2). The module hides the existence of symlinks by inside
87 : lstat(), open(), and readdir() so long as it's not a POSIX
88 : pathname request (those requests *must* be aware of symlinks
89 : and the POSIX client has to follow them, it's expected that
90 : a server will always fail to follow symlinks).
91 :
92 : It does this by:
93 :
94 : 2a). lstat -> stat
95 : 2b). open removes any O_NOFOLLOW from flags.
96 : 2c). The optimization in readdir that returns a stat
97 : struct is removed as this could return a symlink mode
98 : bit, causing smbd to always call stat/lstat itself on
99 : a pathname (which we'll then use to hide symlinks).
100 :
101 : */
102 :
103 : #include "includes.h"
104 : #include "smbd/smbd.h"
105 : #include "lib/util_path.h"
106 :
107 : struct widelinks_config {
108 : bool active;
109 : bool is_dfs_share;
110 : char *cwd;
111 : };
112 :
113 258 : static int widelinks_connect(struct vfs_handle_struct *handle,
114 : const char *service,
115 : const char *user)
116 : {
117 : struct widelinks_config *config;
118 : int ret;
119 :
120 258 : ret = SMB_VFS_NEXT_CONNECT(handle,
121 : service,
122 : user);
123 258 : if (ret != 0) {
124 0 : return ret;
125 : }
126 :
127 258 : config = talloc_zero(handle->conn,
128 : struct widelinks_config);
129 258 : if (!config) {
130 0 : SMB_VFS_NEXT_DISCONNECT(handle);
131 0 : return -1;
132 : }
133 258 : config->active = lp_widelinks(SNUM(handle->conn));
134 258 : if (!config->active) {
135 0 : DBG_ERR("vfs_widelinks module loaded with "
136 : "widelinks = no\n");
137 : }
138 258 : config->is_dfs_share =
139 258 : (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn)));
140 258 : SMB_VFS_HANDLE_SET_DATA(handle,
141 : config,
142 : NULL, /* free_fn */
143 : struct widelinks_config,
144 : return -1);
145 258 : return 0;
146 : }
147 :
148 1822 : static int widelinks_chdir(struct vfs_handle_struct *handle,
149 : const struct smb_filename *smb_fname)
150 : {
151 1822 : int ret = -1;
152 1822 : struct widelinks_config *config = NULL;
153 1822 : char *new_cwd = NULL;
154 :
155 1822 : SMB_VFS_HANDLE_GET_DATA(handle,
156 : config,
157 : struct widelinks_config,
158 : return -1);
159 :
160 1822 : if (!config->active) {
161 : /* Module not active. */
162 0 : return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
163 : }
164 :
165 : /*
166 : * We know we never get a path containing
167 : * DOT or DOTDOT.
168 : */
169 :
170 1822 : if (smb_fname->base_name[0] == '/') {
171 : /* Absolute path - replace. */
172 1768 : new_cwd = talloc_strdup(config,
173 1768 : smb_fname->base_name);
174 : } else {
175 54 : if (config->cwd == NULL) {
176 : /*
177 : * Relative chdir before absolute one -
178 : * see note 1b above.
179 : */
180 : struct smb_filename *current_dir_fname =
181 0 : SMB_VFS_NEXT_GETWD(handle,
182 : config);
183 0 : if (current_dir_fname == NULL) {
184 0 : return -1;
185 : }
186 : /* Paranoia.. */
187 0 : if (current_dir_fname->base_name[0] != '/') {
188 0 : DBG_ERR("SMB_VFS_NEXT_GETWD returned "
189 : "non-absolute path |%s|\n",
190 : current_dir_fname->base_name);
191 0 : TALLOC_FREE(current_dir_fname);
192 0 : return -1;
193 : }
194 0 : config->cwd = talloc_strdup(config,
195 0 : current_dir_fname->base_name);
196 0 : TALLOC_FREE(current_dir_fname);
197 0 : if (config->cwd == NULL) {
198 0 : return -1;
199 : }
200 : }
201 54 : new_cwd = talloc_asprintf(config,
202 : "%s/%s",
203 : config->cwd,
204 54 : smb_fname->base_name);
205 : }
206 1822 : if (new_cwd == NULL) {
207 0 : return -1;
208 : }
209 1822 : ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
210 1822 : if (ret == -1) {
211 196 : TALLOC_FREE(new_cwd);
212 196 : return ret;
213 : }
214 : /* Replace the cache we use for realpath/getwd. */
215 1626 : TALLOC_FREE(config->cwd);
216 1626 : config->cwd = new_cwd;
217 1626 : DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
218 1626 : return 0;
219 : }
220 :
221 1685 : static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
222 : TALLOC_CTX *ctx)
223 : {
224 1685 : struct widelinks_config *config = NULL;
225 :
226 1685 : SMB_VFS_HANDLE_GET_DATA(handle,
227 : config,
228 : struct widelinks_config,
229 : return NULL);
230 :
231 1685 : if (!config->active) {
232 : /* Module not active. */
233 0 : return SMB_VFS_NEXT_GETWD(handle, ctx);
234 : }
235 1685 : if (config->cwd == NULL) {
236 : /* getwd before chdir. See note 1b above. */
237 0 : return SMB_VFS_NEXT_GETWD(handle, ctx);
238 : }
239 1685 : return synthetic_smb_fname(ctx,
240 1685 : config->cwd,
241 : NULL,
242 : NULL,
243 : 0,
244 : 0);
245 : }
246 :
247 1170 : static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
248 : TALLOC_CTX *ctx,
249 : const struct smb_filename *smb_fname_in)
250 : {
251 1170 : struct widelinks_config *config = NULL;
252 1170 : char *pathname = NULL;
253 1170 : char *resolved_pathname = NULL;
254 : struct smb_filename *smb_fname;
255 :
256 1170 : SMB_VFS_HANDLE_GET_DATA(handle,
257 : config,
258 : struct widelinks_config,
259 : return NULL);
260 :
261 1170 : if (!config->active) {
262 : /* Module not active. */
263 0 : return SMB_VFS_NEXT_REALPATH(handle,
264 : ctx,
265 : smb_fname_in);
266 : }
267 :
268 1170 : if (config->cwd == NULL) {
269 : /* realpath before chdir. See note 1b above. */
270 626 : return SMB_VFS_NEXT_REALPATH(handle,
271 : ctx,
272 : smb_fname_in);
273 : }
274 :
275 544 : if (smb_fname_in->base_name[0] == '/') {
276 : /* Absolute path - process as-is. */
277 102 : pathname = talloc_strdup(config,
278 102 : smb_fname_in->base_name);
279 : } else {
280 : /* Relative path - most commonly "." */
281 442 : pathname = talloc_asprintf(config,
282 : "%s/%s",
283 : config->cwd,
284 442 : smb_fname_in->base_name);
285 : }
286 :
287 544 : SMB_ASSERT(pathname[0] == '/');
288 :
289 544 : resolved_pathname = canonicalize_absolute_path(config, pathname);
290 544 : if (resolved_pathname == NULL) {
291 0 : TALLOC_FREE(pathname);
292 0 : return NULL;
293 : }
294 :
295 544 : DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
296 : smb_fname_in->base_name,
297 : pathname,
298 : resolved_pathname);
299 :
300 544 : smb_fname = synthetic_smb_fname(ctx,
301 : resolved_pathname,
302 : NULL,
303 : NULL,
304 : 0,
305 : 0);
306 544 : TALLOC_FREE(pathname);
307 544 : TALLOC_FREE(resolved_pathname);
308 544 : return smb_fname;
309 : }
310 :
311 3162 : static int widelinks_lstat(vfs_handle_struct *handle,
312 : struct smb_filename *smb_fname)
313 : {
314 3162 : struct widelinks_config *config = NULL;
315 :
316 3162 : SMB_VFS_HANDLE_GET_DATA(handle,
317 : config,
318 : struct widelinks_config,
319 : return -1);
320 :
321 3162 : if (!config->active) {
322 : /* Module not active. */
323 0 : return SMB_VFS_NEXT_LSTAT(handle,
324 : smb_fname);
325 : }
326 :
327 3162 : if (config->cwd == NULL) {
328 : /* lstat before chdir. See note 1b above. */
329 0 : return SMB_VFS_NEXT_LSTAT(handle,
330 : smb_fname);
331 : }
332 :
333 3162 : if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
334 : /* POSIX sees symlinks. */
335 0 : return SMB_VFS_NEXT_LSTAT(handle,
336 : smb_fname);
337 : }
338 :
339 : /* Replace with STAT. */
340 3162 : return SMB_VFS_NEXT_STAT(handle, smb_fname);
341 : }
342 :
343 2920 : static int widelinks_openat(vfs_handle_struct *handle,
344 : const struct files_struct *dirfsp,
345 : const struct smb_filename *smb_fname,
346 : files_struct *fsp,
347 : const struct vfs_open_how *_how)
348 : {
349 2920 : struct vfs_open_how how = *_how;
350 2920 : struct widelinks_config *config = NULL;
351 : int ret;
352 2920 : SMB_VFS_HANDLE_GET_DATA(handle,
353 : config,
354 : struct widelinks_config,
355 : return -1);
356 :
357 2920 : if (config->active &&
358 2920 : (config->cwd != NULL) &&
359 2920 : !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
360 : {
361 : /*
362 : * Module active, openat after chdir (see note 1b above) and not
363 : * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
364 : */
365 2920 : how.flags = (how.flags & ~O_NOFOLLOW);
366 : }
367 :
368 2920 : ret = SMB_VFS_NEXT_OPENAT(handle,
369 : dirfsp,
370 : smb_fname,
371 : fsp,
372 : &how);
373 2920 : if (config->is_dfs_share && ret == -1 && errno == ENOENT) {
374 25 : struct smb_filename *full_fname = NULL;
375 : int lstat_ret;
376 :
377 25 : full_fname = full_path_from_dirfsp_atname(talloc_tos(),
378 : dirfsp,
379 : smb_fname);
380 25 : if (full_fname == NULL) {
381 0 : errno = ENOMEM;
382 0 : return -1;
383 : }
384 25 : lstat_ret = SMB_VFS_NEXT_LSTAT(handle,
385 : full_fname);
386 25 : if (lstat_ret != -1 &&
387 4 : VALID_STAT(full_fname->st) &&
388 4 : S_ISLNK(full_fname->st.st_ex_mode)) {
389 4 : fsp->fsp_name->st = full_fname->st;
390 : }
391 25 : TALLOC_FREE(full_fname);
392 25 : errno = ELOOP;
393 : }
394 2920 : return ret;
395 : }
396 :
397 : static struct vfs_fn_pointers vfs_widelinks_fns = {
398 : .connect_fn = widelinks_connect,
399 :
400 : .openat_fn = widelinks_openat,
401 : .lstat_fn = widelinks_lstat,
402 : /*
403 : * NB. We don't need an lchown function as this
404 : * is only called (a) on directory create and
405 : * (b) on POSIX extensions names.
406 : */
407 : .chdir_fn = widelinks_chdir,
408 : .getwd_fn = widelinks_getwd,
409 : .realpath_fn = widelinks_realpath,
410 : };
411 :
412 : static_decl_vfs;
413 273 : NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
414 : {
415 273 : return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
416 : "widelinks",
417 : &vfs_widelinks_fns);
418 : }
|