Gentoo Archives: gentoo-commits

From: Mike Frysinger <vapier@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/sandbox:master commit in: libsandbox/
Date: Thu, 28 Oct 2021 03:41:47
Message-Id: 1635391020.49e6eb50d1c77f06d8b4c728cd222d3d404c8d08.vapier@gentoo
1 commit: 49e6eb50d1c77f06d8b4c728cd222d3d404c8d08
2 Author: Mike Frysinger <vapier <AT> chromium <DOT> org>
3 AuthorDate: Thu Oct 28 03:17:00 2021 +0000
4 Commit: Mike Frysinger <vapier <AT> gentoo <DOT> org>
5 CommitDate: Thu Oct 28 03:17:00 2021 +0000
6 URL: https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=49e6eb50
7
8 libsandbox: drop lstat check for symlink funcs
9
10 When checking paths for violations, we need to know whether the path
11 is a symlink, and whether the current function dereferences them. If
12 it dereferences, we have to check the symlink and its target. If it
13 doesn't, we can skip the target check.
14
15 The helper to see if the function operates on symlinks ends with an
16 lstat on the path itself -- if it exists and is a symlink, we will
17 skip the target check. If it doesn't exist, or isn't a symlink, we
18 check the target. This logic doesn't make sense since (1) if it
19 doesn't exist, or isn't a symlink, there is no "target" and (2) the
20 symlink nature of the function is unchanged.
21
22 In practice, this largely doesn't matter. If the path wasn't a
23 symlink, and it (as the source) already passed checks, then it's
24 also going to pass checks (as the target) since they're the same
25 path.
26
27 However, we get into a fun TOCTOU race: if there are multiple things
28 trying to create a symlink at the same path, then we can get into a
29 state where:
30 - process 1 calls a symlink func on a path doesn't exist
31 - lstat fails, so symlink_func() returns false
32 - the kernel contexts switches away from process 1
33 - process 2 calls a symlink func on the same path
34 - lstat fails, so symlink_func() returns false
35 - the target path is "resolved" and passes validation
36 - process 2 creates the symlink to a place like /usr/bin/foo
37 - process 1 resumes
38 - the target path is resolved since it now actually exists
39 - the target is a bad path (/usr/bin/foo)
40 - sandbox denies the access even though it's a func that only
41 operates on symlinks and never dereferences
42
43 This scenario too rarely happens (causes it's so weird), but it is
44 possible. A quick way to reproduce is with:
45 while [[ ! -e $SANDBOX_LOG ]] ; do
46 ln -s /bin/bash ./f &
47 ln -s /bin/bash ./f &
48 ln -s /bin/bash ./f &
49 ln -s /bin/bash ./f &
50 ln -s /bin/bash ./f &
51 rm -f f
52 wait
53 done
54
55 Eventually this will manage to trigger the TOCTOU race.
56
57 So just delete the lstat check in the symlink_func() helper. If the
58 path doesn't exist, we can safely let it fail. If the path shows up
59 in parallel, either as a symlink or not, we already validated it as
60 being safe, so letting the func be called is safe.
61
62 Bug: https://issuetracker.google.com/issues/204375293
63 Signed-off-by: Mike Frysinger <vapier <AT> gentoo.org>
64
65 libsandbox/libsandbox.c | 51 ++++++++++++++++++++++---------------------------
66 1 file changed, 23 insertions(+), 28 deletions(-)
67
68 diff --git a/libsandbox/libsandbox.c b/libsandbox/libsandbox.c
69 index 4e92cbe..b4db9ba 100644
70 --- a/libsandbox/libsandbox.c
71 +++ b/libsandbox/libsandbox.c
72 @@ -671,36 +671,31 @@ static int check_prefixes(char **prefixes, int num_prefixes, const char *path)
73 }
74
75 /* Is this a func that works on symlinks, and is the file a symlink ? */
76 -static bool symlink_func(int sb_nr, int flags, const char *abs_path)
77 +static bool symlink_func(int sb_nr, int flags)
78 {
79 - struct stat st;
80 -
81 /* These funcs always operate on symlinks */
82 - if (!(sb_nr == SB_NR_UNLINK ||
83 - sb_nr == SB_NR_UNLINKAT ||
84 - sb_nr == SB_NR_LCHOWN ||
85 - sb_nr == SB_NR_LREMOVEXATTR ||
86 - sb_nr == SB_NR_LSETXATTR ||
87 - sb_nr == SB_NR_REMOVE ||
88 - sb_nr == SB_NR_RENAME ||
89 - sb_nr == SB_NR_RENAMEAT ||
90 - sb_nr == SB_NR_RENAMEAT2 ||
91 - sb_nr == SB_NR_RMDIR ||
92 - sb_nr == SB_NR_SYMLINK ||
93 - sb_nr == SB_NR_SYMLINKAT))
94 - {
95 - /* These funcs sometimes operate on symlinks */
96 - if (!((sb_nr == SB_NR_FCHOWNAT ||
97 - sb_nr == SB_NR_FCHMODAT ||
98 - sb_nr == SB_NR_UTIMENSAT) &&
99 - (flags & AT_SYMLINK_NOFOLLOW)))
100 - return false;
101 - }
102 + if (sb_nr == SB_NR_UNLINK ||
103 + sb_nr == SB_NR_UNLINKAT ||
104 + sb_nr == SB_NR_LCHOWN ||
105 + sb_nr == SB_NR_LREMOVEXATTR ||
106 + sb_nr == SB_NR_LSETXATTR ||
107 + sb_nr == SB_NR_REMOVE ||
108 + sb_nr == SB_NR_RENAME ||
109 + sb_nr == SB_NR_RENAMEAT ||
110 + sb_nr == SB_NR_RENAMEAT2 ||
111 + sb_nr == SB_NR_RMDIR ||
112 + sb_nr == SB_NR_SYMLINK ||
113 + sb_nr == SB_NR_SYMLINKAT)
114 + return true;
115
116 - if (-1 != lstat(abs_path, &st) && S_ISLNK(st.st_mode))
117 + /* These funcs sometimes operate on symlinks */
118 + if ((sb_nr == SB_NR_FCHOWNAT ||
119 + sb_nr == SB_NR_FCHMODAT ||
120 + sb_nr == SB_NR_UTIMENSAT) &&
121 + (flags & AT_SYMLINK_NOFOLLOW))
122 return true;
123 - else
124 - return false;
125 +
126 + return false;
127 }
128
129 static int check_access(sbcontext_t *sbcontext, int sb_nr, const char *func,
130 @@ -709,7 +704,7 @@ static int check_access(sbcontext_t *sbcontext, int sb_nr, const char *func,
131 int old_errno = errno;
132 int result = 0;
133 int retval;
134 - bool sym_func = symlink_func(sb_nr, flags, abs_path);
135 + bool sym_func = symlink_func(sb_nr, flags);
136
137 retval = check_prefixes(sbcontext->deny_prefixes,
138 sbcontext->num_deny_prefixes, abs_path);
139 @@ -904,7 +899,7 @@ static int check_syscall(sbcontext_t *sbcontext, int sb_nr, const char *func,
140 * itself does not dereference. This speeds things up and avoids updating
141 * the atime implicitly. #415475
142 */
143 - if (symlink_func(sb_nr, flags, absolute_path))
144 + if (symlink_func(sb_nr, flags))
145 resolved_path = absolute_path;
146 else
147 resolved_path = resolve_path(file, 1);