Skip to content

Commit dfee424

Browse files
committed
Add support for reading symlinks longer than PATH_MAX to readlink and readlinkat
1 parent a937988 commit dfee424

File tree

2 files changed

+67
-19
lines changed

2 files changed

+67
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4343
when a group or user lookup required a buffer larger than
4444
16KB. (#[1198](https://github.com/nix-rust/nix/pull/1198))
4545
- Fixed unaligned casting of `cmsg_data` to `af_alg_iv` (#[1206](https://github.com/nix-rust/nix/pull/1206))
46+
- Fixed `readlink`/`readlinkat` when reading symlinks longer than `PATH_MAX` (#[1231](https://github.com/nix-rust/nix/pull/1231))
4647

4748
### Removed
4849

src/fcntl.rs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -180,33 +180,80 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(old_dirfd: Option<Ra
180180
Errno::result(res).map(drop)
181181
}
182182

183-
fn wrap_readlink_result(v: &mut Vec<u8>, res: ssize_t) -> Result<OsString> {
184-
match Errno::result(res) {
185-
Err(err) => Err(err),
186-
Ok(len) => {
187-
unsafe { v.set_len(len as usize) }
188-
Ok(OsString::from_vec(v.to_vec()))
183+
fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
184+
unsafe { v.set_len(len as usize) }
185+
v.shrink_to_fit();
186+
Ok(OsString::from_vec(v.to_vec()))
187+
}
188+
189+
fn readlink_maybe_at<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P,
190+
v: &mut Vec<u8>)
191+
-> Result<libc::ssize_t> {
192+
path.with_nix_path(|cstr| {
193+
unsafe {
194+
match dirfd {
195+
Some(dirfd) => libc::readlinkat(dirfd, cstr.as_ptr(),
196+
v.as_mut_ptr() as *mut c_char,
197+
v.capacity() as size_t),
198+
None => libc::readlink(cstr.as_ptr(),
199+
v.as_mut_ptr() as *mut c_char,
200+
v.capacity() as size_t),
201+
}
189202
}
190-
}
203+
})
191204
}
192205

193-
pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
206+
fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P)
207+
-> Result<OsString> {
194208
let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
195-
let res = path.with_nix_path(|cstr| {
196-
unsafe { libc::readlink(cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, v.capacity() as size_t) }
197-
})?;
198-
199-
wrap_readlink_result(&mut v, res)
209+
// simple case: result is strictly less than `PATH_MAX`
210+
let res = readlink_maybe_at(dirfd, path, &mut v)?;
211+
let len = Errno::result(res)?;
212+
debug_assert!(len >= 0);
213+
if (len as usize) < v.capacity() {
214+
return wrap_readlink_result(v, res);
215+
}
216+
// Uh oh, the result is too long...
217+
// Let's try to ask lstat how many bytes to allocate.
218+
let reported_size = super::sys::stat::lstat(path)
219+
.and_then(|x| Ok(x.st_size)).unwrap_or(0);
220+
let mut try_size = if reported_size > 0 {
221+
// Note: even if `lstat`'s apparently valid answer turns out to be
222+
// wrong, we will still read the full symlink no matter what.
223+
reported_size as usize + 1
224+
} else {
225+
// If lstat doesn't cooperate, or reports an error, be a little less
226+
// precise.
227+
(libc::PATH_MAX as usize).max(128) << 1
228+
};
229+
loop {
230+
v.reserve_exact(try_size);
231+
let res = readlink_maybe_at(dirfd, path, &mut v)?;
232+
let len = Errno::result(res)?;
233+
debug_assert!(len >= 0);
234+
if (len as usize) < v.capacity() {
235+
break wrap_readlink_result(v, res);
236+
}
237+
else {
238+
// Ugh! Still not big enough!
239+
match try_size.checked_shl(1) {
240+
Some(next_size) => try_size = next_size,
241+
// It's absurd that this would happen, but handle it sanely
242+
// anyway.
243+
None => break Err(super::Error::Sys(Errno::ENAMETOOLONG))
244+
}
245+
}
246+
}
200247
}
201248

249+
pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
250+
inner_readlink(None, path)
251+
}
202252

203-
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
204-
let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
205-
let res = path.with_nix_path(|cstr| {
206-
unsafe { libc::readlinkat(dirfd, cstr.as_ptr(), v.as_mut_ptr() as *mut c_char, v.capacity() as size_t) }
207-
})?;
208253

209-
wrap_readlink_result(&mut v, res)
254+
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P)
255+
-> Result<OsString> {
256+
inner_readlink(Some(dirfd), path)
210257
}
211258

212259
/// Computes the raw fd consumed by a function of the form `*at`.

0 commit comments

Comments
 (0)