diff --git a/CHANGELOG.md b/CHANGELOG.md index d50dffc..021331d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ Run [`cargo-release`][cr] to publish a release. ## [Unreleased] +### Changed +- Depend on rustix instead of libc to get rid of unsafe code (see [GH-24]). + +[GH-24]: https://github.com/swsnr/systemd-journal-logger.rs/pull/24 + ## [2.0.0] – 2023-10-01 ### Added diff --git a/Cargo.toml b/Cargo.toml index ab38085..c081179 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.71" # feature unconditionally. We require std for the set_boxed_logger field in # init_with_extra_fields. log = { version = "^0.4", features = ["std", "kv_unstable"] } -libc = "0.2.148" +rustix = { version = "0.38.20", default-features = false, features = ["std", "fs", "net"] } [dev-dependencies] similar-asserts = "1.5.0" diff --git a/src/client.rs b/src/client.rs index cbb1f30..46da00d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,14 +8,28 @@ //! A journald client. +use std::fs::File; use std::io::prelude::*; use std::os::fd::AsFd; use std::os::unix::net::UnixDatagram; -use crate::{memfd, socket}; +use rustix::fs::fcntl_add_seals; +use rustix::fs::memfd_create; +use rustix::fs::MemfdFlags; +use rustix::fs::SealFlags; +use rustix::io::Errno; +use rustix::net::sendmsg_unix; +use rustix::net::SendAncillaryBuffer; +use rustix::net::SendFlags; +use rustix::net::SocketAddrUnix; const JOURNALD_PATH: &str = "/run/systemd/journal/socket"; +// We use a static buffer size, since we don't send arbitrary control messages +// here; we just need enough space to send a single file descriptor. This way +// we get away without additional allocations. +const CMSG_BUFSIZE: usize = 64; + pub struct JournalClient { socket: UnixDatagram, } @@ -39,7 +53,7 @@ impl JournalClient { self.socket .send_to(payload, JOURNALD_PATH) .or_else(|error| { - if Some(libc::EMSGSIZE) == error.raw_os_error() { + if Some(Errno::MSGSIZE) == Errno::from_io_error(&error) { self.send_large_payload(payload) } else { Err(error) @@ -51,11 +65,32 @@ impl JournalClient { // If the payload's too large for a single datagram, send it through a memfd, see // https://systemd.io/JOURNAL_NATIVE_PROTOCOL/ // Write the whole payload to a memfd - let mut mem = memfd::create_sealable()?; + let mut mem: File = memfd_create( + "systemd-journal-logger", + MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC, + )? + .into(); mem.write_all(payload)?; // Fully seal the memfd to signal journald that its backing data won't resize anymore // and so is safe to mmap. - memfd::seal_fully(mem.as_fd())?; - socket::send_one_fd_to(&self.socket, mem.as_fd(), JOURNALD_PATH) + fcntl_add_seals( + &mem, + SealFlags::SEAL | SealFlags::SHRINK | SealFlags::WRITE | SealFlags::GROW, + )?; + // Send the FD over to journald. + let fds = &[mem.as_fd()]; + let scm_rights = rustix::net::SendAncillaryMessage::ScmRights(fds); + let mut buffer = [0; CMSG_BUFSIZE]; + assert!(scm_rights.size() <= buffer.len()); + let mut buffer = SendAncillaryBuffer::new(&mut buffer); + assert!(buffer.push(scm_rights), "Failed to push ScmRights message"); + let size = sendmsg_unix( + &self.socket, + &SocketAddrUnix::new(JOURNALD_PATH)?, + &[], + &mut buffer, + SendFlags::NOSIGNAL, + )?; + Ok(size) } } diff --git a/src/lib.rs b/src/lib.rs index 4d6ca53..7457ca1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,7 @@ //! output formats of `journalctl`, e.g. `journalctl --output=json`. #![deny(warnings, missing_docs, clippy::all)] +#![forbid(unsafe_code)] use std::io::prelude::*; use std::os::fd::AsFd; @@ -70,8 +71,6 @@ use log::{Level, Log, Metadata, Record, SetLoggerError}; mod client; mod fields; -mod memfd; -mod socket; use fields::*; diff --git a/src/memfd.rs b/src/memfd.rs deleted file mode 100644 index 044dfe7..0000000 --- a/src/memfd.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright Sebastian Wiesner -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Work with memfds. - -// Taken from -// which I wrote, ie own copyright, and thus can freely relicense it. - -use std::fs::File; -use std::io::{Error, Result}; -use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd}; - -use libc::{ - c_char, c_uint, fcntl, memfd_create, F_ADD_SEALS, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK, - F_SEAL_WRITE, MFD_ALLOW_SEALING, MFD_CLOEXEC, -}; - -fn create(flags: c_uint) -> Result { - // SAFETY: memfd_create allocates a new file descriptor. The name is a static string, so we can safely convert to a pointer. - let fd = unsafe { memfd_create("tracing-journald\0".as_ptr() as *const c_char, flags) }; - if fd < 0 { - Err(Error::last_os_error()) - } else { - // SAFETY: We created fd above, so it belongs to us now. We also checked that it's a valid fd. - Ok(unsafe { OwnedFd::from_raw_fd(fd) }) - } -} - -/// Create a sealable memfd. -pub fn create_sealable() -> Result { - create(MFD_ALLOW_SEALING | MFD_CLOEXEC).map(File::from) -} - -pub fn seal_fully(fd: BorrowedFd) -> Result<()> { - let all_seals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL; - // SAFETY: fnctl does not take ownership of the file descriptor, so we can convert to a raw file descriptor. - // SAFETY: We don't pass any pointers here, so there's no potential for memory issues. - let result = unsafe { fcntl(fd.as_raw_fd(), F_ADD_SEALS, all_seals) }; - if result < 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } -} diff --git a/src/socket.rs b/src/socket.rs deleted file mode 100644 index f4001e0..0000000 --- a/src/socket.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright Sebastian Wiesner -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Utiltities for working with sockets. - -// Taken from https://github.com/tokio-rs/tracing/blob/2e84c0b2a39d7c5276ab14e757a016e948099144/tracing-journald/src/socket.rs -// which I wrote, ie own copyright, and thus can freely relicense it. - -use std::io::{Error, Result}; -use std::mem::{size_of, zeroed}; -use std::os::fd::BorrowedFd; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::net::UnixDatagram; -use std::os::unix::prelude::{AsRawFd, RawFd}; -use std::path::Path; -use std::ptr; - -use libc::*; - -// We use a static buffer size, since we don't send arbitrary control messages -// here; we just need enough space to send a single file descriptor. This is a -// lot simpler than the `CMSG_SPACE` dance. -// -// We check the size to avoid memory issues, in a test, as well as immediately -// at the beginning of `send_one_fd`. -const CMSG_BUFSIZE: usize = 64; - -#[repr(C)] -union AlignedBuffer { - buffer: T, - align: cmsghdr, -} - -fn assert_cmsg_bufsize() { - let space_one_fd = unsafe { CMSG_SPACE(size_of::() as u32) }; - assert!( - space_one_fd <= CMSG_BUFSIZE as u32, - "cmsghdr buffer too small (< {}) to hold a single fd", - space_one_fd - ); -} - -#[cfg(test)] -#[test] -fn cmsg_buffer_size_for_one_fd() { - assert_cmsg_bufsize() -} - -/// Send one file descriptor over `socket` to `path`. -/// -/// Note that the FD is copied, i.e. the calling process retains ownership of -/// `fd`, and needs to take care to close it. -pub fn send_one_fd_to>( - socket: &UnixDatagram, - fd: BorrowedFd, - path: P, -) -> Result { - assert_cmsg_bufsize(); - - let mut addr: sockaddr_un = unsafe { zeroed() }; - let path_bytes = path.as_ref().as_os_str().as_bytes(); - // path_bytes may have at most sun_path + 1 bytes, to account for the trailing NUL byte. - if addr.sun_path.len() <= path_bytes.len() { - return Err(Error::from_raw_os_error(ENAMETOOLONG)); - } - - addr.sun_family = AF_UNIX as _; - unsafe { - std::ptr::copy_nonoverlapping( - path_bytes.as_ptr(), - addr.sun_path.as_mut_ptr() as *mut u8, - path_bytes.len(), - ) - }; - - let mut msg: msghdr = unsafe { zeroed() }; - // Set the target address. - msg.msg_name = &mut addr as *mut _ as *mut c_void; - msg.msg_namelen = size_of::() as socklen_t; - - // We send no data body with this message. - msg.msg_iov = ptr::null_mut(); - msg.msg_iovlen = 0; - - // Create and fill the control message buffer with our file descriptor - let mut cmsg_buffer = AlignedBuffer { - buffer: ([0u8; CMSG_BUFSIZE]), - }; - msg.msg_control = unsafe { cmsg_buffer.buffer.as_mut_ptr() as _ }; - msg.msg_controllen = unsafe { CMSG_SPACE(size_of::() as _) as _ }; - - let cmsg: &mut cmsghdr = - unsafe { CMSG_FIRSTHDR(&msg).as_mut() }.expect("Control message buffer exhausted"); - - cmsg.cmsg_level = SOL_SOCKET; - cmsg.cmsg_type = SCM_RIGHTS; - cmsg.cmsg_len = unsafe { CMSG_LEN(size_of::() as _) as _ }; - - unsafe { ptr::write(CMSG_DATA(cmsg) as *mut RawFd, fd.as_raw_fd()) }; - - let result = unsafe { sendmsg(socket.as_raw_fd(), &msg, libc::MSG_NOSIGNAL) }; - - if result < 0 { - Err(Error::last_os_error()) - } else { - // sendmsg returns the number of bytes written - Ok(result as usize) - } -}