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); |