mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-27 02:17:08 +00:00
Avoid setting executable permissions on files we might not own (#5582)
## Summary If we just created an entrypoint script, we can of course set the permissions (we just created it). However, if we're copying from the cache, we might _not_ own the file. In that case, if we need to change the permissions (we shouldn't, since the script is likely already executable -- we set the permissions when we unzip, but I guess they could _not_ be properly set in the zip itself), we have to copy it. Closes https://github.com/astral-sh/uv/issues/5581.
This commit is contained in:
parent
dfb4e5bbc8
commit
750b3a7c8c
5 changed files with 98 additions and 28 deletions
|
|
@ -597,7 +597,7 @@ fn symlink_wheel_files(
|
|||
|
||||
// The `RECORD` file is modified during installation, so we copy it instead of symlinking.
|
||||
if path.ends_with("RECORD") {
|
||||
fs::copy(path, &out_path)?;
|
||||
synchronized_copy(path, &out_path, locks)?;
|
||||
count += 1;
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -327,11 +327,14 @@ pub(crate) fn write_script_entrypoints(
|
|||
// Make the launcher executable.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(
|
||||
site_packages.join(entrypoint_relative),
|
||||
std::fs::Permissions::from_mode(0o755),
|
||||
)?;
|
||||
|
||||
let path = site_packages.join(entrypoint_relative);
|
||||
let permissions = fs::metadata(&path)?.permissions();
|
||||
if permissions.mode() & 0o111 != 0o111 {
|
||||
fs::set_permissions(path, Permissions::from_mode(permissions.mode() | 0o111))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -534,20 +537,64 @@ fn install_script(
|
|||
)
|
||||
})?;
|
||||
fs::remove_file(&path)?;
|
||||
|
||||
// Make the script executable. We just created the file, so we can set permissions directly.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let permissions = fs::metadata(&script_absolute)?.permissions();
|
||||
if permissions.mode() & 0o111 != 0o111 {
|
||||
fs::set_permissions(
|
||||
script_absolute,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Some(size_and_encoded_hash)
|
||||
} else {
|
||||
// reading and writing is slow especially for large binaries, so we move them instead
|
||||
// Reading and writing is slow (especially for large binaries), so we move them instead, if
|
||||
// we can. This also retains the file permissions. We _can't_ move (and must copy) if the
|
||||
// file permissions need to be changed, since we might not own the file.
|
||||
drop(script);
|
||||
fs::rename(&path, &script_absolute)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let permissions = fs::metadata(&path)?.permissions();
|
||||
|
||||
if permissions.mode() & 0o111 == 0o111 {
|
||||
// If the permissions are already executable, we don't need to change them.
|
||||
fs::rename(&path, &script_absolute)?;
|
||||
} else {
|
||||
// If we have to modify the permissions, copy the file, since we might not own it.
|
||||
warn!(
|
||||
"Copying script from {} to {} (permissions: {:o})",
|
||||
path.simplified_display(),
|
||||
script_absolute.simplified_display(),
|
||||
permissions.mode()
|
||||
);
|
||||
|
||||
uv_fs::copy_atomic_sync(&path, &script_absolute)?;
|
||||
|
||||
fs::set_permissions(
|
||||
script_absolute,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
fs::rename(&path, &script_absolute)?;
|
||||
}
|
||||
|
||||
None
|
||||
};
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
fs::set_permissions(&script_absolute, Permissions::from_mode(0o755))?;
|
||||
}
|
||||
|
||||
// Find the existing entry in the `RECORD`.
|
||||
let relative_to_site_packages = path
|
||||
|
|
|
|||
|
|
@ -87,11 +87,13 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
|
|||
let path = target.join(path);
|
||||
|
||||
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
|
||||
fs_err::tokio::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)
|
||||
.await?;
|
||||
if permissions.mode() & 0o111 != 0o111 {
|
||||
fs_err::tokio::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,11 +139,13 @@ async fn untar_in<R: tokio::io::AsyncRead + Unpin, P: AsRef<Path>>(
|
|||
if has_any_executable_bit != 0 {
|
||||
if let Some(path) = crate::tar::unpacked_at(dst.as_ref(), &file.path()?) {
|
||||
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
|
||||
fs_err::tokio::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)
|
||||
.await?;
|
||||
if permissions.mode() & 0o111 != 0o111 {
|
||||
fs_err::tokio::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,10 +69,12 @@ pub fn unzip<R: Send + std::io::Read + std::io::Seek + HasLength>(
|
|||
let has_any_executable_bit = mode & 0o111;
|
||||
if has_any_executable_bit != 0 {
|
||||
let permissions = fs_err::metadata(&path)?.permissions();
|
||||
fs_err::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)?;
|
||||
if permissions.mode() & 0o111 != 0o111 {
|
||||
fs_err::set_permissions(
|
||||
&path,
|
||||
Permissions::from_mode(permissions.mode() | 0o111),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,6 +161,23 @@ pub fn write_atomic_sync(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> std:
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy `from` to `to` atomically using a temporary file and atomic rename.
|
||||
pub fn copy_atomic_sync(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
let temp_file = tempfile_in(to.as_ref().parent().expect("Write path must have a parent"))?;
|
||||
fs_err::copy(from.as_ref(), &temp_file)?;
|
||||
temp_file.persist(&to).map_err(|err| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"Failed to persist temporary file to {}: {}",
|
||||
to.user_display(),
|
||||
err.error
|
||||
),
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rename a file, retrying (on Windows) if it fails due to transient operating system errors.
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn rename_with_retry(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue