Skip to main content

detcore_model/
futex.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::fd::OpenFileId;
13use crate::pid::DetTid;
14
15/// Identity of a Linux memory address space (`mm_struct`).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct MmId {
18    creator: DetTid,
19    generation: u64,
20}
21
22impl MmId {
23    /// Create the initial address space owned by a task.
24    pub const fn initial(creator: DetTid) -> Self {
25        Self {
26            creator,
27            generation: 0,
28        }
29    }
30
31    /// Inherit an address space for `CLONE_VM`, otherwise create a fresh identity.
32    pub const fn for_clone(parent: Self, child: DetTid, shares_vm: bool) -> Self {
33        if shares_vm {
34            parent
35        } else {
36            Self::initial(child)
37        }
38    }
39
40    /// Create the replacement address space installed by exec.
41    pub fn for_exec(self, creator: DetTid) -> Self {
42        Self {
43            creator,
44            // Exec generations must remain monotonic even when Linux changes a
45            // non-leader caller's TID to the process leader's TID.
46            generation: self.generation + 1,
47        }
48    }
49}
50
51/// Identity of an object that backs a process-shared memory mapping.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum SharedMemoryObjectId {
54    /// Anonymous shared mapping, identified by its deterministic allocation.
55    Anonymous {
56        /// Address space in which the mapping was created.
57        origin: MmId,
58        /// Per-address-space mapping sequence.
59        sequence: u64,
60    },
61    /// File-backed shared mapping.
62    File {
63        /// Device containing the file.
64        device: u64,
65        /// Inode of the file.
66        inode: u64,
67    },
68    /// Open-file-description fallback used when inode metadata is unavailable.
69    OpenFile { id: OpenFileId },
70}
71
72/// Identity of a futex word.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub enum FutexID {
75    /// Process-private futex, keyed by its address space and virtual address.
76    Private { mm: MmId, address: usize },
77    /// Process-shared futex, keyed by its backing object and byte offset.
78    Shared {
79        object: SharedMemoryObjectId,
80        offset: u64,
81    },
82}
83
84impl FutexID {
85    /// Create a private futex key.
86    pub const fn private(mm: MmId, address: usize) -> Self {
87        Self::Private { mm, address }
88    }
89
90    /// Create a process-shared futex key.
91    pub const fn shared(object: SharedMemoryObjectId, offset: u64) -> Self {
92        Self::Shared { object, offset }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn clone_vm_controls_private_futex_aliasing() {
102        let parent = MmId::initial(DetTid::from_raw(10));
103        let child = DetTid::from_raw(11);
104
105        assert_eq!(
106            FutexID::private(parent, 0x1000),
107            FutexID::private(MmId::for_clone(parent, child, true), 0x1000),
108            "CLONE_VM tasks should use the same private futex key"
109        );
110        assert_ne!(
111            FutexID::private(parent, 0x1000),
112            FutexID::private(MmId::for_clone(parent, child, false), 0x1000),
113            "a copied address space should not alias the parent's private futex"
114        );
115        assert_ne!(
116            FutexID::private(parent, 0x1000),
117            FutexID::private(parent.for_exec(DetTid::from_raw(10)), 0x1000),
118            "exec should replace the private futex namespace"
119        );
120    }
121
122    #[test]
123    fn exec_generation_never_repeats_when_caller_identity_changes() {
124        let leader = DetTid::from_raw(10);
125        let worker = DetTid::from_raw(11);
126        let initial = MmId::initial(leader);
127        let worker_exec = initial.for_exec(worker);
128        let leader_exec = worker_exec.for_exec(leader);
129
130        assert_ne!(worker_exec, initial);
131        assert_ne!(leader_exec, initial);
132        assert_ne!(leader_exec, worker_exec);
133    }
134
135    #[test]
136    fn shared_futexes_alias_by_object_and_offset() {
137        let object = SharedMemoryObjectId::File {
138            device: 10,
139            inode: 20,
140        };
141        assert_eq!(
142            FutexID::shared(object, 64),
143            FutexID::shared(object, 64),
144            "virtual-address aliases of one backing offset must share a key"
145        );
146        assert_ne!(
147            FutexID::shared(object, 64),
148            FutexID::shared(object, 68),
149            "different words in one backing object need distinct keys"
150        );
151        assert_ne!(
152            FutexID::shared(object, 64),
153            FutexID::private(MmId::initial(DetTid::from_raw(10)), 64),
154            "private and shared namespaces must remain distinct"
155        );
156    }
157}