Skip to main content

starnix_core/device/
mem.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::device::kobject::DeviceMetadata;
6use crate::device::{DeviceMode, simple_device_ops};
7use crate::mm::{
8    DesiredAddress, MappingName, MappingOptions, MemoryAccessorExt, ProtectionFlags,
9    create_anonymous_mapping_memory,
10};
11use crate::task::syslog::{self, KmsgLevel};
12use crate::task::{
13    CurrentTask, EventHandler, Kernel, LogSubscription, Syslog, SyslogAccess, WaitCanceler, Waiter,
14};
15use crate::vfs::buffers::{InputBuffer, InputBufferExt as _, OutputBuffer};
16use crate::vfs::{
17    Anon, FileHandle, FileObject, FileOps, NamespaceNode, SeekTarget, fileops_impl_noop_sync,
18    fileops_impl_seekless,
19};
20use starnix_logging::{Level, track_stub};
21use starnix_sync::{DevKmsgLock, LockDepMutex};
22use starnix_uapi::device_id::DeviceId;
23use starnix_uapi::error;
24use starnix_uapi::errors::Errno;
25use starnix_uapi::open_flags::OpenFlags;
26use starnix_uapi::user_address::UserAddress;
27use starnix_uapi::vfs::FdEvents;
28use std::mem::MaybeUninit;
29
30#[derive(Default)]
31pub struct DevNull;
32
33pub fn new_null_file(current_task: &CurrentTask, flags: OpenFlags) -> FileHandle {
34    Anon::new_private_file(current_task, Box::new(DevNull), flags, "[fuchsia:null]")
35}
36
37impl FileOps for DevNull {
38    fileops_impl_seekless!();
39    fileops_impl_noop_sync!();
40
41    fn write(
42        &self,
43        _file: &FileObject,
44        _current_task: &CurrentTask,
45        _offset: usize,
46        data: &mut dyn InputBuffer,
47    ) -> Result<usize, Errno> {
48        // TODO(https://fxbug.dev/453758455) align /dev/null behavior with Linux
49        // Writes to /dev/null on Linux treat the input buffer in an unconventional way. The actual
50        // data is not touched and if the input parameters are plausible the device claims to
51        // successfully write up to MAX_RW_COUNT bytes.  If the input parameters are outside of the
52        // user accessible address space, writes will return EFAULT.
53        let bytes_logged = match data.read_to_vec_limited(data.available()) {
54            Ok(bytes) => bytes.len(),
55            Err(_) => 0,
56        };
57
58        Ok(bytes_logged + data.drain())
59    }
60
61    fn read(
62        &self,
63        _file: &FileObject,
64        _current_task: &CurrentTask,
65        _offset: usize,
66        _data: &mut dyn OutputBuffer,
67    ) -> Result<usize, Errno> {
68        Ok(0)
69    }
70
71    fn to_handle(
72        &self,
73        _file: &FileObject,
74        _current_task: &CurrentTask,
75    ) -> Result<Option<zx::NullableHandle>, Errno> {
76        Ok(None)
77    }
78
79    fn can_query_events_without_wait(&self) -> bool {
80        // Always ready: `wait_async` and `query_events` are the stateless defaults.
81        true
82    }
83}
84
85#[derive(Default)]
86struct DevZero;
87impl FileOps for DevZero {
88    fileops_impl_seekless!();
89    fileops_impl_noop_sync!();
90
91    fn mmap(
92        &self,
93        file: &FileObject,
94        current_task: &CurrentTask,
95        addr: DesiredAddress,
96        memory_offset: u64,
97        length: usize,
98        prot_flags: ProtectionFlags,
99        mut options: MappingOptions,
100    ) -> Result<UserAddress, Errno> {
101        // All /dev/zero mappings behave as anonymous mappings.
102        //
103        // This means that we always create a new zero-filled VMO for this mmap request.
104        // Memory is never shared between two mappings of /dev/zero, even if
105        // `MappingOptions::SHARED` is set.
106        //
107        // Similar to anonymous mappings, if this process were to request a shared mapping
108        // of /dev/zero and then fork, the child and the parent process would share the
109        // VMO created here.
110        let memory = create_anonymous_mapping_memory(length as u64)?;
111
112        options |= MappingOptions::ANONYMOUS;
113
114        current_task.mm()?.map_memory(
115            addr,
116            memory,
117            memory_offset,
118            length,
119            prot_flags,
120            options,
121            // We set the filename here, even though we are creating what is
122            // functionally equivalent to an anonymous mapping. Doing so affects
123            // the output of `/proc/self/maps` and identifies this mapping as
124            // file-based.
125            MappingName::File(file.to_mapping(None)?),
126        )
127    }
128
129    fn write(
130        &self,
131        _file: &FileObject,
132        _current_task: &CurrentTask,
133        _offset: usize,
134        data: &mut dyn InputBuffer,
135    ) -> Result<usize, Errno> {
136        Ok(data.drain())
137    }
138
139    fn read(
140        &self,
141        _file: &FileObject,
142        _current_task: &CurrentTask,
143        _offset: usize,
144        data: &mut dyn OutputBuffer,
145    ) -> Result<usize, Errno> {
146        data.zero()
147    }
148}
149
150#[derive(Default)]
151struct DevFull;
152impl FileOps for DevFull {
153    fileops_impl_seekless!();
154    fileops_impl_noop_sync!();
155
156    fn write(
157        &self,
158        _file: &FileObject,
159        _current_task: &CurrentTask,
160        _offset: usize,
161        _data: &mut dyn InputBuffer,
162    ) -> Result<usize, Errno> {
163        error!(ENOSPC)
164    }
165
166    fn read(
167        &self,
168        _file: &FileObject,
169        _current_task: &CurrentTask,
170        _offset: usize,
171        data: &mut dyn OutputBuffer,
172    ) -> Result<usize, Errno> {
173        data.write_each(&mut |bytes| {
174            bytes.fill(MaybeUninit::new(0));
175            Ok(bytes.len())
176        })
177    }
178}
179
180struct ZeroOnDropBuffer {
181    buf: Vec<u8>,
182}
183
184impl ZeroOnDropBuffer {
185    fn new(size: usize) -> Self {
186        Self { buf: vec![0u8; size] }
187    }
188}
189
190impl Drop for ZeroOnDropBuffer {
191    fn drop(&mut self) {
192        // SAFETY: The pointers constructed here are bounded by the lifetime of self.buf.
193        unsafe {
194            for i in 0..self.buf.len() {
195                std::ptr::write_volatile(self.buf.as_mut_ptr().add(i), 0);
196            }
197        }
198    }
199}
200
201#[derive(Default)]
202pub struct DevRandom;
203impl FileOps for DevRandom {
204    fileops_impl_seekless!();
205    fileops_impl_noop_sync!();
206
207    fn write(
208        &self,
209        _file: &FileObject,
210        _current_task: &CurrentTask,
211        _offset: usize,
212        data: &mut dyn InputBuffer,
213    ) -> Result<usize, Errno> {
214        Ok(data.drain())
215    }
216
217    fn read(
218        &self,
219        _file: &FileObject,
220        _current_task: &CurrentTask,
221        _offset: usize,
222        data: &mut dyn OutputBuffer,
223    ) -> Result<usize, Errno> {
224        // Zero out the memory where we stored the random values as they may be used to seed keys
225        // or other cryptographic data structures and we do not want to accidentally leak the data.
226        let mut rdm = ZeroOnDropBuffer::new(data.available());
227        starnix_crypto::cprng_draw(&mut rdm.buf);
228        data.write(&rdm.buf)
229    }
230
231    fn ioctl(
232        &self,
233        _file: &FileObject,
234        current_task: &CurrentTask,
235        request: u32,
236        arg: starnix_syscalls::SyscallArg,
237    ) -> Result<starnix_syscalls::SyscallResult, Errno> {
238        match request {
239            starnix_uapi::RNDGETENTCNT => {
240                let addr = starnix_uapi::user_address::UserRef::<i32>::new(UserAddress::from(arg));
241                // Linux just returns 256 no matter what (as observed on 6.5.6).
242                let result = 256;
243                current_task.write_object(addr, &result).map(|_| starnix_syscalls::SUCCESS)
244            }
245            _ => error!(ENOTTY),
246        }
247    }
248}
249
250pub fn open_kmsg(
251    current_task: &CurrentTask,
252    _id: DeviceId,
253    _node: &NamespaceNode,
254    flags: OpenFlags,
255) -> Result<Box<dyn FileOps>, Errno> {
256    if flags.can_read() {
257        Syslog::validate_access(current_task, SyslogAccess::DevKmsgRead)?;
258    }
259    let subscription = if flags.can_read() {
260        Some(Syslog::snapshot_then_subscribe(current_task.kernel())?.into())
261    } else {
262        None
263    };
264    Ok(Box::new(DevKmsg(subscription)))
265}
266
267struct DevKmsg(Option<LockDepMutex<LogSubscription, DevKmsgLock>>);
268
269impl FileOps for DevKmsg {
270    fileops_impl_noop_sync!();
271
272    fn has_persistent_offsets(&self) -> bool {
273        false
274    }
275
276    fn is_seekable(&self) -> bool {
277        true
278    }
279
280    fn seek(
281        &self,
282        _file: &crate::vfs::FileObject,
283        current_task: &crate::task::CurrentTask,
284        _current_offset: starnix_uapi::off_t,
285        target: crate::vfs::SeekTarget,
286    ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> {
287        match target {
288            SeekTarget::Set(0) => {
289                let Some(ref subscription) = self.0 else {
290                    return Ok(0);
291                };
292                let mut guard = subscription.lock();
293                *guard = Syslog::snapshot_then_subscribe(current_task.kernel())?;
294                Ok(0)
295            }
296            SeekTarget::End(0) => {
297                let Some(ref subscription) = self.0 else {
298                    return Ok(0);
299                };
300                let mut guard = subscription.lock();
301                *guard = Syslog::subscribe(current_task.kernel())?;
302                Ok(0)
303            }
304            SeekTarget::Data(0) => {
305                track_stub!(TODO("https://fxbug.dev/322874315"), "/dev/kmsg: SEEK_DATA");
306                Ok(0)
307            }
308            // The following are implemented as documented on:
309            // https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg
310            // The only accepted seek targets are "SEEK_END,0", "SEEK_SET,0" and "SEEK_DATA,0"
311            // When given an invalid offset, ESPIPE is expected.
312            SeekTarget::End(_) | SeekTarget::Set(_) | SeekTarget::Data(_) => {
313                error!(ESPIPE, "Unsupported offset")
314            }
315            // According to the docs above and observations, this should be EINVAL, but dprintf
316            // fails if we make it EINVAL.
317            SeekTarget::Cur(_) => error!(ESPIPE),
318            SeekTarget::Hole(_) => error!(EINVAL, "Unsupported seek target"),
319        }
320    }
321
322    fn wait_async(
323        &self,
324        _file: &FileObject,
325        _current_task: &CurrentTask,
326        waiter: &Waiter,
327        events: FdEvents,
328        handler: EventHandler,
329    ) -> Option<WaitCanceler> {
330        self.0.as_ref().map(|subscription| subscription.lock().wait(waiter, events, handler))
331    }
332
333    fn query_events(
334        &self,
335        _file: &FileObject,
336        _current_task: &CurrentTask,
337    ) -> Result<FdEvents, Errno> {
338        let mut events = FdEvents::empty();
339        if let Some(subscription) = self.0.as_ref() {
340            if subscription.lock().available()? > 0 {
341                events |= FdEvents::POLLIN;
342            }
343        }
344        Ok(events)
345    }
346
347    fn read(
348        &self,
349        file: &FileObject,
350        current_task: &CurrentTask,
351        _offset: usize,
352        data: &mut dyn OutputBuffer,
353    ) -> Result<usize, Errno> {
354        file.blocking_op(current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, || {
355            match self.0.as_ref().unwrap().lock().next() {
356                Some(Ok(log)) => data.write(&log),
357                Some(Err(err)) => Err(err),
358                None => Ok(0),
359            }
360        })
361    }
362
363    fn write(
364        &self,
365        _file: &FileObject,
366        _current_task: &CurrentTask,
367        _offset: usize,
368        data: &mut dyn InputBuffer,
369    ) -> Result<usize, Errno> {
370        let bytes = data.read_all()?;
371        let extract_result = syslog::extract_level(&bytes);
372        let (level, msg_bytes) = match extract_result {
373            None => (Level::Info, bytes.as_slice()),
374            Some((level, bytes_after_level)) => match level {
375                // An error but keep the <level> str.
376                KmsgLevel::Emergency | KmsgLevel::Alert | KmsgLevel::Critical => {
377                    (Level::Error, bytes.as_slice())
378                }
379                KmsgLevel::Error => (Level::Error, bytes_after_level),
380                KmsgLevel::Warning => (Level::Warn, bytes_after_level),
381                // Log as info but show the <level>.
382                KmsgLevel::Notice => (Level::Info, bytes.as_slice()),
383                KmsgLevel::Info => (Level::Info, bytes_after_level),
384                KmsgLevel::Debug => (Level::Debug, bytes_after_level),
385            },
386        };
387
388        // We need to create and emit our own log record here, because the log macros will include
389        // a file and line by default if the log message is ERROR level. This file/line is not
390        // relevant to log messages forwarded from userspace, and the kmsg tag is hopefully enough
391        // to distinguish messages forwarded this way.
392        starnix_logging::with_current_task_info(|info| {
393            starnix_logging::logger().log(
394                // The log::RecordBuilder API only allows providing the body of a log message as
395                // format_args!(), which cannot be assigned to bindings if it captures values
396                // (https://doc.rust-lang.org/std/macro.format_args.html#lifetime-limitation).
397                // So this creates the record in the same expression where it is used.
398                &starnix_logging::Record::builder()
399                    .level(level)
400                    .key_values(&[
401                        ("tag", LogOutputTag::Str("kmsg")),
402                        ("tag", LogOutputTag::Display(info)),
403                    ])
404                    .args(format_args!(
405                        "{}",
406                        String::from_utf8_lossy(msg_bytes).trim_end_matches('\n')
407                    ))
408                    .build(),
409            );
410        });
411        Ok(bytes.len())
412    }
413}
414
415enum LogOutputTag<'a> {
416    Str(&'a str),
417    Display(&'a dyn std::fmt::Display),
418}
419
420impl<'a> starnix_logging::ToValue for LogOutputTag<'a> {
421    fn to_value(&self) -> starnix_logging::Value<'_> {
422        match self {
423            Self::Str(s) => starnix_logging::Value::from_display(s),
424            Self::Display(d) => starnix_logging::Value::from_dyn_display(d),
425        }
426    }
427}
428
429pub fn mem_device_init(kernel: &Kernel) -> Result<(), Errno> {
430    let registry = &kernel.device_registry;
431
432    let mem_class = registry.objects.mem_class();
433    registry.add_device(
434        kernel,
435        "null".into(),
436        DeviceMetadata::new("null".into(), DeviceId::NULL, DeviceMode::Char),
437        mem_class.clone(),
438        simple_device_ops::<DevNull>,
439    );
440    registry.add_device(
441        kernel,
442        "zero".into(),
443        DeviceMetadata::new("zero".into(), DeviceId::ZERO, DeviceMode::Char),
444        mem_class.clone(),
445        simple_device_ops::<DevZero>,
446    );
447    registry.add_device(
448        kernel,
449        "full".into(),
450        DeviceMetadata::new("full".into(), DeviceId::FULL, DeviceMode::Char),
451        mem_class.clone(),
452        simple_device_ops::<DevFull>,
453    );
454    registry.add_device(
455        kernel,
456        "random".into(),
457        DeviceMetadata::new("random".into(), DeviceId::RANDOM, DeviceMode::Char),
458        mem_class.clone(),
459        simple_device_ops::<DevRandom>,
460    );
461    registry.add_device(
462        kernel,
463        "urandom".into(),
464        DeviceMetadata::new("urandom".into(), DeviceId::URANDOM, DeviceMode::Char),
465        mem_class.clone(),
466        simple_device_ops::<DevRandom>,
467    );
468    registry.add_device(
469        kernel,
470        "kmsg".into(),
471        DeviceMetadata::new("kmsg".into(), DeviceId::KMSG, DeviceMode::Char),
472        mem_class,
473        open_kmsg,
474    );
475    Ok(())
476}