Make control identity mode umask-independent

Set and validate the publication identity through its retained descriptor so restrictive process masks cannot weaken or remove the required mode.
This commit is contained in:
ms2k 2026-07-15 18:11:02 +09:00
commit 18afbeaa91

View file

@ -28,7 +28,7 @@ use rcgen::{
DistinguishedName, DnType, DnValue, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose,
PKCS_ED25519,
};
use rustix::fs::{Mode, OFlags, openat};
use rustix::fs::{Mode, OFlags, fchmod, openat};
use rustix::net::sockopt::socket_peercred;
use rustls::pki_types::{
CertificateDer, CertificateSigningRequestDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName,
@ -630,8 +630,12 @@ impl ControlPaths {
Ok(())
}
fn create_identity(&self, bytes: &[u8]) -> Result<(File, FileIdentity), ControlError> {
self.validate_root(self.root_file.metadata()?.uid())?;
fn create_identity(
&self,
bytes: &[u8],
uid: u32,
) -> Result<(File, FileIdentity), ControlError> {
self.validate_root(uid)?;
let fd = openat(
self.root_file.as_fd(),
PUBLICATION_IDENTITY_NAME,
@ -640,15 +644,51 @@ impl ControlPaths {
)
.map_err(io::Error::from)?;
let mut file = File::from(fd);
file.write_all(bytes)
.map_err(|_| ControlError::InvalidPublication("identity write failed"))?;
file.sync_all()
.map_err(|_| ControlError::InvalidPublication("identity sync failed"))?;
self.root_file
.sync_all()
.map_err(|_| ControlError::InvalidPublication("directory sync failed"))?;
let metadata = file.metadata()?;
Ok((file, FileIdentity::from_metadata(&metadata)))
let identity = FileIdentity::from_metadata(&file.metadata()?);
let result = (|| {
fchmod(file.as_fd(), Mode::RUSR | Mode::WUSR).map_err(io::Error::from)?;
file.write_all(bytes)
.map_err(|_| ControlError::InvalidPublication("identity write failed"))?;
file.sync_all()
.map_err(|_| ControlError::InvalidPublication("identity sync failed"))?;
self.root_file
.sync_all()
.map_err(|_| ControlError::InvalidPublication("directory sync failed"))?;
self.validate_identity(&file, identity, uid, bytes.len())
})();
if let Err(error) = result {
remove_if_identity_matches(&self.identity, identity);
return Err(error);
}
Ok((file, identity))
}
fn validate_identity(
&self,
file: &File,
expected: FileIdentity,
uid: u32,
expected_length: usize,
) -> Result<(), ControlError> {
self.validate_root(uid)?;
let descriptor = file.metadata()?;
let current = std::fs::symlink_metadata(&self.identity)?;
if FileIdentity::from_metadata(&descriptor) != expected
|| FileIdentity::from_metadata(&current) != expected
|| !descriptor.file_type().is_file()
|| !current.file_type().is_file()
|| descriptor.uid() != uid
|| current.uid() != uid
|| descriptor.mode() & 0o7777 != 0o600
|| current.mode() & 0o7777 != 0o600
|| usize::try_from(descriptor.len()).ok() != Some(expected_length)
|| current.len() != descriptor.len()
{
return Err(ControlError::InvalidPublication(
"publication identity changed type, ownership, mode, or content length",
));
}
Ok(())
}
}
@ -692,7 +732,8 @@ impl PublishedControlFace {
let publication_identity = anchor.publication_identity();
let publication_bytes = encode(&publication_identity)?;
let server_config = Arc::new(anchor.server_config()?);
let (identity_file, identity_file_identity) = paths.create_identity(&publication_bytes)?;
let (identity_file, identity_file_identity) =
paths.create_identity(&publication_bytes, uid)?;
let listener = match std::os::unix::net::UnixListener::bind(&paths.socket) {
Ok(listener) => listener,
Err(error) => {
@ -730,25 +771,16 @@ impl PublishedControlFace {
return Err(error);
}
};
let identity_unchanged = identity_file
.metadata()
.map(|metadata| FileIdentity::from_metadata(&metadata) == identity_file_identity);
match identity_unchanged {
Ok(true) => {}
Ok(false) => {
drop(listener);
let _ = std::fs::remove_file(&paths.socket);
let _ = std::fs::remove_file(&paths.identity);
return Err(ControlError::InvalidPublication(
"publication identity changed during creation",
));
}
Err(error) => {
drop(listener);
let _ = std::fs::remove_file(&paths.socket);
let _ = std::fs::remove_file(&paths.identity);
return Err(error.into());
}
if let Err(error) = paths.validate_identity(
&identity_file,
identity_file_identity,
uid,
publication_bytes.len(),
) {
drop(listener);
let _ = std::fs::remove_file(&paths.socket);
remove_if_identity_matches(&paths.identity, identity_file_identity);
return Err(error);
}
Ok(Self {
listener,