CVE-2023-0614 ldb: Centralise checking for inaccessible matches
[Samba.git] / source3 / modules / vfs_widelinks.c
blob1cbb0bf0ad03b98bfcc988ad480d0652bd8457a0
1 /*
2 * Widelinks VFS module. Causes smbd not to see symlinks.
4 * Copyright (C) Jeremy Allison, 2020
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.
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.
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/>.
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.
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.
29 The smbd code can now always return ACCESS_DENIED if a path
30 leads outside a share.
32 How does it do that ? There are 2 features.
34 1). When the upper layer code does a chdir() call to a pathname,
35 this module stores the requested pathname inside config->cwd.
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.
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.
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.
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:
61 if (!config->active) {
62 Module not active.
63 SMB_VFS_NEXT_XXXXX(...)
66 idiom in the vfs functions.
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:
79 if (config->cwd == NULL) {
80 XXXXX syscall before chdir - see note 1b above.
81 return SMB_VFS_NEXT_XXXXX()
84 idiom in the vfs functions.
86 2). The module hides the existance 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).
92 It does this by:
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).
103 #include "includes.h"
104 #include "smbd/smbd.h"
105 #include "lib/util_path.h"
107 struct widelinks_config {
108 bool active;
109 char *cwd;
112 static int widelinks_connect(struct vfs_handle_struct *handle,
113 const char *service,
114 const char *user)
116 struct widelinks_config *config;
117 int ret;
119 ret = SMB_VFS_NEXT_CONNECT(handle,
120 service,
121 user);
122 if (ret != 0) {
123 return ret;
126 config = talloc_zero(handle->conn,
127 struct widelinks_config);
128 if (!config) {
129 SMB_VFS_NEXT_DISCONNECT(handle);
130 return -1;
132 config->active = lp_widelinks(SNUM(handle->conn));
133 if (!config->active) {
134 DBG_ERR("vfs_widelinks module loaded with "
135 "widelinks = no\n");
138 SMB_VFS_HANDLE_SET_DATA(handle,
139 config,
140 NULL, /* free_fn */
141 struct widelinks_config,
142 return -1);
143 return 0;
146 static int widelinks_chdir(struct vfs_handle_struct *handle,
147 const struct smb_filename *smb_fname)
149 int ret = -1;
150 struct widelinks_config *config = NULL;
151 char *new_cwd = NULL;
153 SMB_VFS_HANDLE_GET_DATA(handle,
154 config,
155 struct widelinks_config,
156 return -1);
158 if (!config->active) {
159 /* Module not active. */
160 return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
164 * We know we never get a path containing
165 * DOT or DOTDOT.
168 if (smb_fname->base_name[0] == '/') {
169 /* Absolute path - replace. */
170 new_cwd = talloc_strdup(config,
171 smb_fname->base_name);
172 } else {
173 if (config->cwd == NULL) {
175 * Relative chdir before absolute one -
176 * see note 1b above.
178 struct smb_filename *current_dir_fname =
179 SMB_VFS_NEXT_GETWD(handle,
180 config);
181 if (current_dir_fname == NULL) {
182 return -1;
184 /* Paranoia.. */
185 if (current_dir_fname->base_name[0] != '/') {
186 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
187 "non-absolute path |%s|\n",
188 current_dir_fname->base_name);
189 TALLOC_FREE(current_dir_fname);
190 return -1;
192 config->cwd = talloc_strdup(config,
193 current_dir_fname->base_name);
194 TALLOC_FREE(current_dir_fname);
195 if (config->cwd == NULL) {
196 return -1;
199 new_cwd = talloc_asprintf(config,
200 "%s/%s",
201 config->cwd,
202 smb_fname->base_name);
204 if (new_cwd == NULL) {
205 return -1;
207 ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
208 if (ret == -1) {
209 TALLOC_FREE(new_cwd);
210 return ret;
212 /* Replace the cache we use for realpath/getwd. */
213 TALLOC_FREE(config->cwd);
214 config->cwd = new_cwd;
215 DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
216 return 0;
219 static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
220 TALLOC_CTX *ctx)
222 struct widelinks_config *config = NULL;
224 SMB_VFS_HANDLE_GET_DATA(handle,
225 config,
226 struct widelinks_config,
227 return NULL);
229 if (!config->active) {
230 /* Module not active. */
231 return SMB_VFS_NEXT_GETWD(handle, ctx);
233 if (config->cwd == NULL) {
234 /* getwd before chdir. See note 1b above. */
235 return SMB_VFS_NEXT_GETWD(handle, ctx);
237 return synthetic_smb_fname(ctx,
238 config->cwd,
239 NULL,
240 NULL,
245 static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
246 TALLOC_CTX *ctx,
247 const struct smb_filename *smb_fname_in)
249 struct widelinks_config *config = NULL;
250 char *pathname = NULL;
251 char *resolved_pathname = NULL;
252 struct smb_filename *smb_fname;
254 SMB_VFS_HANDLE_GET_DATA(handle,
255 config,
256 struct widelinks_config,
257 return NULL);
259 if (!config->active) {
260 /* Module not active. */
261 return SMB_VFS_NEXT_REALPATH(handle,
262 ctx,
263 smb_fname_in);
266 if (config->cwd == NULL) {
267 /* realpath before chdir. See note 1b above. */
268 return SMB_VFS_NEXT_REALPATH(handle,
269 ctx,
270 smb_fname_in);
273 if (smb_fname_in->base_name[0] == '/') {
274 /* Absolute path - process as-is. */
275 pathname = talloc_strdup(config,
276 smb_fname_in->base_name);
277 } else {
278 /* Relative path - most commonly "." */
279 pathname = talloc_asprintf(config,
280 "%s/%s",
281 config->cwd,
282 smb_fname_in->base_name);
285 SMB_ASSERT(pathname[0] == '/');
287 resolved_pathname = canonicalize_absolute_path(config, pathname);
288 if (resolved_pathname == NULL) {
289 TALLOC_FREE(pathname);
290 return NULL;
293 DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
294 smb_fname_in->base_name,
295 pathname,
296 resolved_pathname);
298 smb_fname = synthetic_smb_fname(ctx,
299 resolved_pathname,
300 NULL,
301 NULL,
304 TALLOC_FREE(pathname);
305 TALLOC_FREE(resolved_pathname);
306 return smb_fname;
309 static int widelinks_lstat(vfs_handle_struct *handle,
310 struct smb_filename *smb_fname)
312 struct widelinks_config *config = NULL;
314 SMB_VFS_HANDLE_GET_DATA(handle,
315 config,
316 struct widelinks_config,
317 return -1);
319 if (!config->active) {
320 /* Module not active. */
321 return SMB_VFS_NEXT_LSTAT(handle,
322 smb_fname);
325 if (config->cwd == NULL) {
326 /* lstat before chdir. See note 1b above. */
327 return SMB_VFS_NEXT_LSTAT(handle,
328 smb_fname);
331 if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
332 /* POSIX sees symlinks. */
333 return SMB_VFS_NEXT_LSTAT(handle,
334 smb_fname);
337 /* Replace with STAT. */
338 return SMB_VFS_NEXT_STAT(handle, smb_fname);
341 static int widelinks_openat(vfs_handle_struct *handle,
342 const struct files_struct *dirfsp,
343 const struct smb_filename *smb_fname,
344 files_struct *fsp,
345 int flags,
346 mode_t mode)
348 struct widelinks_config *config = NULL;
350 SMB_VFS_HANDLE_GET_DATA(handle,
351 config,
352 struct widelinks_config,
353 return -1);
355 if (config->active &&
356 (config->cwd != NULL) &&
357 !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
360 * Module active, openat after chdir (see note 1b above) and not
361 * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
363 flags = (flags & ~O_NOFOLLOW);
366 return SMB_VFS_NEXT_OPENAT(handle,
367 dirfsp,
368 smb_fname,
369 fsp,
370 flags,
371 mode);
374 static struct dirent *widelinks_readdir(vfs_handle_struct *handle,
375 struct files_struct *dirfsp,
376 DIR *dirp,
377 SMB_STRUCT_STAT *sbuf)
379 struct widelinks_config *config = NULL;
380 struct dirent *result;
382 SMB_VFS_HANDLE_GET_DATA(handle,
383 config,
384 struct widelinks_config,
385 return NULL);
387 result = SMB_VFS_NEXT_READDIR(handle,
388 dirfsp,
389 dirp,
390 sbuf);
392 if (!config->active) {
393 /* Module not active. */
394 return result;
398 * Prevent optimization of returning
399 * the stat info. Force caller to go
400 * through our LSTAT that hides symlinks.
403 if (sbuf) {
404 SET_STAT_INVALID(*sbuf);
406 return result;
409 static struct vfs_fn_pointers vfs_widelinks_fns = {
410 .connect_fn = widelinks_connect,
412 .openat_fn = widelinks_openat,
413 .lstat_fn = widelinks_lstat,
415 * NB. We don't need an lchown function as this
416 * is only called (a) on directory create and
417 * (b) on POSIX extensions names.
419 .chdir_fn = widelinks_chdir,
420 .getwd_fn = widelinks_getwd,
421 .realpath_fn = widelinks_realpath,
422 .readdir_fn = widelinks_readdir
425 static_decl_vfs;
426 NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
428 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
429 "widelinks",
430 &vfs_widelinks_fns);