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:
Charlie Marsh 2024-07-30 08:32:52 -04:00 committed by GitHub
parent dfb4e5bbc8
commit 750b3a7c8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 98 additions and 28 deletions

View file

@ -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;
}

View file

@ -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