55
66#include " linyaps_box/utils/inspect.h"
77#include " linyaps_box/utils/log.h"
8+ #include " linyaps_box/utils/symlink.h"
89
910#include < sys/syscall.h>
1011
1112#include < cassert>
1213#include < fstream>
14+ #include < optional>
1315
1416#ifdef LINYAPS_BOX_HAVE_OPENAT2_H
1517# include < linux/openat2.h>
2628#endif
2729
2830namespace {
31+
32+ // see: https://man7.org/linux/man-pages/man7/path_resolution.7.html
33+ constexpr auto MAX_SYMLINK_DEPTH = 40 ;
34+
35+ auto stat_same_inode (const struct stat &a, const struct stat &b) noexcept -> bool
36+ {
37+ return a.st_dev == b.st_dev && a.st_ino == b.st_ino ;
38+ }
39+
40+ auto check_not_above_root (const linyaps_box::utils::file_descriptor ¤t,
41+ const linyaps_box::utils::file_descriptor &root) -> void
42+ {
43+ auto cur_stat = linyaps_box::utils::fstat (current);
44+ auto root_stat = linyaps_box::utils::fstat (root);
45+ if (stat_same_inode (cur_stat, root_stat)) {
46+ throw std::system_error (EACCES ,
47+ std::system_category (),
48+ " path escape detected: attempted to go above root" );
49+ }
50+ }
51+
52+ auto walk_component (linyaps_box::utils::file_descriptor &dir_fd,
53+ const std::string &component,
54+ const linyaps_box::utils::file_descriptor &root,
55+ int symlink_depth) -> void;
56+
57+ auto resolve_symlink_component (linyaps_box::utils::file_descriptor &dir_fd,
58+ linyaps_box::utils::file_descriptor &link_fd,
59+ const linyaps_box::utils::file_descriptor &root,
60+ int symlink_depth) -> void
61+ {
62+ if (UNLIKELY (symlink_depth > MAX_SYMLINK_DEPTH )) {
63+ throw std::system_error (ELOOP , std::system_category (), " too many symlinks" );
64+ }
65+
66+ auto target = linyaps_box::utils::readlinkat (link_fd, " " );
67+ LINYAPS_BOX_DEBUG () << " resolve symlink -> " << target;
68+
69+ if (target.is_absolute ()) {
70+ dir_fd = root.duplicate ();
71+ }
72+
73+ for (const auto &part : target.relative_path ()) {
74+ walk_component (dir_fd, part.string (), root, symlink_depth);
75+ }
76+ }
77+
78+ auto walk_component (linyaps_box::utils::file_descriptor &dir_fd,
79+ const std::string &component,
80+ const linyaps_box::utils::file_descriptor &root,
81+ int symlink_depth) -> void
82+ {
83+ if (component == " ." ) {
84+ return ;
85+ }
86+
87+ if (component == " .." ) {
88+ check_not_above_root (dir_fd, root);
89+ auto fd = ::openat (dir_fd.get (), " .." , O_PATH | O_CLOEXEC | O_NOFOLLOW );
90+ if (UNLIKELY (fd < 0 )) {
91+ throw std::system_error (errno,
92+ std::system_category (),
93+ " openat: failed to open parent directory" );
94+ }
95+
96+ linyaps_box::utils::file_descriptor parent_fd{ fd };
97+ dir_fd = std::move (parent_fd);
98+ return ;
99+ }
100+
101+ auto fd = ::openat (dir_fd.get (), component.c_str (), O_PATH | O_CLOEXEC | O_NOFOLLOW );
102+ if (UNLIKELY (fd < 0 )) {
103+ throw std::system_error (errno,
104+ std::system_category (),
105+ std::string{ " openat: failed to open component " } + component);
106+ }
107+
108+ linyaps_box::utils::file_descriptor child_fd{ fd };
109+ auto child_stat = linyaps_box::utils::fstat (child_fd);
110+
111+ if (S_ISLNK (child_stat.st_mode )) {
112+ resolve_symlink_component (dir_fd, child_fd, root, symlink_depth + 1 );
113+ return ;
114+ }
115+
116+ if (UNLIKELY (!S_ISDIR (child_stat.st_mode ))) {
117+ throw std::system_error (ENOTDIR , std::system_category (), " not a directory: " + component);
118+ }
119+
120+ dir_fd = std::move (child_fd);
121+ }
122+
29123auto open_at_fallback (const linyaps_box::utils::file_descriptor &root,
30124 const std::filesystem::path &path,
31125 int flag,
@@ -35,23 +129,65 @@ auto open_at_fallback(const linyaps_box::utils::file_descriptor &root,
35129 << linyaps_box::utils::inspect_fcntl_or_open_flags (
36130 static_cast <size_t >(flag))
37131 << " \n\t " << linyaps_box::utils::inspect_fd (root.get ());
38- // TODO: we need implement a compatible fallback
39- // currently we just use openat and do some simple check
40- const auto &file_path = path.relative_path ();
41- const auto fd = ::openat (root.get (), file_path.c_str (), flag, mode);
42- if (fd < 0 ) {
43- auto full_path = root.current_path () / path.relative_path ();
132+
133+ auto current = root.duplicate ();
134+ const auto &parts = path.relative_path ();
135+
136+ auto it = parts.begin ();
137+ auto end = parts.end ();
138+ size_t index{ 0 };
139+ auto total = std::distance (it, end);
140+
141+ for (; it != end; ++it, ++index) {
142+ auto component = it->string ();
143+ auto is_last = (index + 1 == static_cast <size_t >(total));
144+
145+ if (component == " ." ) {
146+ continue ;
147+ }
148+
149+ if (component == " .." ) {
150+ walk_component (current, component, root, 0 );
151+ continue ;
152+ }
153+
154+ if (is_last) {
155+ auto fd = ::openat (current.get (), component.c_str (), flag, mode);
156+ if (UNLIKELY (fd < 0 )) {
157+ auto full_path = root.current_path () / path.relative_path ();
158+ throw std::system_error (errno,
159+ std::system_category (),
160+ std::string{ " openat: failed to open " }
161+ + full_path.string ());
162+ }
163+
164+ return linyaps_box::utils::file_descriptor{ fd };
165+ }
166+
167+ walk_component (current, component, root, 0 );
168+ }
169+
170+ auto fd = ::openat (current.get (), " ." , flag, mode);
171+ if (UNLIKELY (fd < 0 )) {
44172 throw std::system_error (errno,
45173 std::system_category (),
46- std::string{ " openat: failed to open " } + full_path. string () );
174+ " openat: failed to open resolved path " );
47175 }
48176
49177 return linyaps_box::utils::file_descriptor{ fd };
50178}
51179
52- auto syscall_openat2 (int dirfd, const char *path, uint64_t flag, uint64_t mode, uint64_t resolve)
53- -> linyaps_box::utils::file_descriptor
180+ // TODO: use expected for better error handling
181+ auto syscall_openat2 (int dirfd,
182+ const char *path,
183+ uint64_t flag,
184+ uint64_t mode,
185+ uint64_t resolve,
186+ std::error_code &ec) noexcept
187+ -> std::optional<linyaps_box::utils::file_descriptor>
54188{
189+ ec.clear ();
190+
55191 struct openat2_how
56192 {
57193 uint64_t flags;
@@ -60,8 +196,9 @@ auto syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode,
60196 } how{ flag, mode, resolve };
61197
62198 const auto ret = syscall (__NR_openat2, dirfd, path, &how, sizeof (openat2_how), 0 );
63- if (ret < 0 ) {
64- throw std::system_error (errno, std::system_category (), " openat2" );
199+ if (UNLIKELY (ret < 0 )) {
200+ ec.assign (errno, std::system_category ());
201+ return std::nullopt ;
65202 }
66203
67204 return linyaps_box::utils::file_descriptor{ static_cast <int >(ret) };
@@ -70,7 +207,7 @@ auto syscall_openat2(int dirfd, const char *path, uint64_t flag, uint64_t mode,
70207auto read_pseudo_file (const std::filesystem::path &path) -> std::string
71208{
72209 std::ifstream ifs (path, std::ios::in | std::ios::binary);
73- if (!ifs) {
210+ if (UNLIKELY ( !ifs) ) {
74211 throw std::runtime_error (" Can't open pseudo file " + path.string ());
75212 }
76213
@@ -87,7 +224,7 @@ auto open(const std::filesystem::path &path, int flag, mode_t mode)
87224 LINYAPS_BOX_DEBUG () << " open " << path.c_str () << " with "
88225 << inspect_fcntl_or_open_flags (static_cast <size_t >(flag));
89226 const auto fd = ::open (path.c_str (), flag, mode);
90- if (fd == -1 ) {
227+ if (UNLIKELY ( fd == -1 ) ) {
91228 throw std::system_error (errno,
92229 std::system_category (),
93230 " open: failed to open " + path.string ());
@@ -105,34 +242,38 @@ auto open_at(const linyaps_box::utils::file_descriptor &root,
105242 << inspect_fcntl_or_open_flags (static_cast <size_t >(flag)) << " \n\t "
106243 << inspect_fd (root.get ());
107244
245+ // currently box is running in single thread, so use bool is safe
108246 static bool support_openat2{ true };
109247 while (support_openat2) {
110- try {
111- return syscall_openat2 (root.get (),
248+ std::error_code ec;
249+
250+ auto ret = syscall_openat2 (root.get (),
112251 path.c_str (),
113252 static_cast <uint64_t >(flag),
114253 mode,
115- RESOLVE_IN_ROOT );
116- } catch (const std::system_error &e) {
117- const auto code = e.code ().value ();
118- if (code == EINTR || code == EAGAIN ) {
254+ RESOLVE_IN_ROOT ,
255+ ec);
256+ if (ec) {
257+ auto val = ec.value ();
258+ if (val == EINTR || val == EAGAIN ) {
119259 continue ;
120260 }
121261
122- if (code == ENOSYS ) {
262+ if (val == ENOSYS ) {
123263 support_openat2 = false ;
124264 break ;
125265 }
126266
127- if (code == EINVAL || code == EPERM ) {
128- break ;
267+ if (val == EINVAL || val == E2BIG ) {
268+ // EINVAL can be an unknown flag or invalid value was specified in how.
269+ // E2BIG can be an extension that this kernel does not support was specified in how
270+ return open_at_fallback (root, path, flag, mode);
129271 }
130272
131- throw std::system_error (code,
132- std::system_category (),
133- std::string{ e.what () } + " : failed to open "
134- + (root.current_path () / path.relative_path ()).string ());
273+ throw std::system_error (ec, " failed to openat2 " + path.string ());
135274 }
275+
276+ return std::move (*ret);
136277 }
137278
138279 // NOTE: openat2 only available after linux 5.15
@@ -144,7 +285,7 @@ auto touch(const file_descriptor &root, const std::filesystem::path &path, int f
144285{
145286 LINYAPS_BOX_DEBUG () << " touch " << path << " at " << inspect_fd (root.get ());
146287 const auto fd = ::openat (root.get (), path.c_str (), flag, mode);
147- if (fd == -1 ) {
288+ if (UNLIKELY ( fd == -1 ) ) {
148289 throw std::system_error (errno,
149290 std::system_category (),
150291 " openat: " + (root.current_path () / path.relative_path ()).string ());
@@ -157,7 +298,7 @@ auto fstat(const file_descriptor &fd) -> struct stat
157298{
158299 struct stat statbuf{ };
159300 auto ret = ::fstat (fd.get (), &statbuf);
160- if (ret == -1 ) {
301+ if (UNLIKELY ( ret == -1 ) ) {
161302 throw std::system_error (errno, std::system_category (), " fstat" );
162303 }
163304
@@ -170,7 +311,7 @@ auto fstatat(const file_descriptor &fd, const std::filesystem::path &path, int f
170311{
171312 struct stat statbuf{ };
172313 auto ret = ::fstatat (fd.get (), path.c_str (), &statbuf, flag);
173- if (ret == -1 ) {
314+ if (UNLIKELY ( ret == -1 ) ) {
174315 throw std::system_error (errno, std::system_category (), " fstatat" );
175316 }
176317
@@ -197,7 +338,7 @@ auto lstat(const std::filesystem::path &path) -> struct stat
197338{
198339 struct stat statbuf{ };
199340 auto ret = ::lstat (path.c_str (), &statbuf);
200- if (ret == -1 ) {
341+ if (UNLIKELY ( ret == -1 ) ) {
201342 throw std::system_error (errno,
202343 std::system_category (),
203344 " lstat " + path.string () + " failed:" );
@@ -212,7 +353,7 @@ auto statfs(const file_descriptor &fd) -> struct statfs
212353{
213354 struct statfs statbuf{ };
214355 auto ret = ::statfs (fd.proc_path ().c_str (), &statbuf);
215- if (ret == -1 ) {
356+ if (UNLIKELY ( ret == -1 ) ) {
216357 throw std::system_error (errno, std::system_category (), " statfs" );
217358 }
218359
0 commit comments