1
//! Implementation logic for `fs-mistrust`.
2

            
3
use std::{
4
    fs::{FileType, Metadata},
5
    path::Path,
6
};
7

            
8
#[cfg(target_family = "unix")]
9
use std::os::unix::prelude::MetadataExt;
10

            
11
use crate::{
12
    Error, Result, Type,
13
    walk::{PathType, ResolvePath},
14
};
15

            
16
/// Definition for the "sticky bit", which on Unix means that the contents of
17
/// directory may not be renamed, deleted, or otherwise modified by a non-owner
18
/// of those contents, even if the user has write permissions on the directory.
19
///
20
/// This is the usual behavior for /tmp: You can make your own directories in
21
/// /tmp, but you can't modify other people's.
22
///
23
/// (We'd use libc's version of `S_ISVTX`, but they vacillate between u16 and
24
/// u32 depending what platform you're on.)
25
#[cfg(target_family = "unix")]
26
pub(crate) const STICKY_BIT: u32 = 0o1000;
27

            
28
/// Helper: Box an iterator of errors.
29
236418
fn boxed<'a, I: Iterator<Item = Error> + 'a>(iter: I) -> Box<dyn Iterator<Item = Error> + 'a> {
30
236418
    Box::new(iter)
31
236418
}
32

            
33
impl<'a> super::Verifier<'a> {
34
    /// Return an iterator of all the security problems with `path`.
35
    ///
36
    /// If the iterator is empty, then there is no problem with `path`.
37
    //
38
    // TODO: This iterator is not fully lazy; sometimes, calls to check_one()
39
    // return multiple errors when it would be better for them to return only
40
    // one (since we're ignoring errors after the first).  This might be nice
41
    // to fix in the future if we can do so without adding much complexity
42
    // to the code.  It's not urgent, since the allocations won't cost much
43
    // compared to the filesystem access.
44
118209
    pub(crate) fn check_errors(&self, path: &Path) -> impl Iterator<Item = Error> + '_ + use<'_> {
45
118209
        if self.mistrust.is_disabled() {
46
            // We don't want to walk the path in this case at all: we'll just
47
            // look at the last element.
48

            
49
94793
            let meta = match path.metadata() {
50
81703
                Ok(meta) => meta,
51
13090
                Err(e) => return boxed(vec![Error::inspecting(e, path)].into_iter()),
52
            };
53
81703
            let mut errors = Vec::new();
54
81703
            self.check_type(path, PathType::Final, &meta, &mut errors);
55
81703
            return boxed(errors.into_iter());
56
23416
        }
57

            
58
23416
        let rp = match ResolvePath::new(path) {
59
23416
            Ok(rp) => rp,
60
            Err(e) => return boxed(vec![e].into_iter()),
61
        };
62

            
63
        // Filter to remove every path that is a prefix of ignore_prefix. (IOW,
64
        // if stop_at_dir is /home/arachnidsGrip, real_stop_at_dir will be
65
        // /home, and we'll ignore / and /home.)
66
108405
        let should_retain = move |r: &Result<_>| match (r, &self.mistrust.ignore_prefix) {
67
67348
            (Ok((p, _, _)), Some(ignore_prefix)) => !ignore_prefix.starts_with(p),
68
40674
            (_, _) => true,
69
108022
        };
70

            
71
23416
        boxed(
72
23416
            rp.filter(should_retain)
73
23416
                // Finally, check the path for errors.
74
23416
                //
75
23416
                // See `check_one` below for a note on TOCTOU issues.
76
59226
                .flat_map(move |r| match r {
77
54205
                    Ok((path, path_type, metadata)) => {
78
54205
                        self.check_one(path.as_path(), path_type, &metadata)
79
                    }
80
4638
                    Err(e) => vec![e],
81
59226
                }),
82
23416
        )
83
118209
    }
84

            
85
    /// If check_contents is set, return an iterator over all the errors in
86
    /// elements _contained in this directory_.
87
    #[cfg(feature = "walkdir")]
88
118209
    pub(crate) fn check_content_errors(
89
118209
        &self,
90
118209
        path: &Path,
91
118209
    ) -> impl Iterator<Item = Error> + '_ + use<'_> {
92
        use std::sync::Arc;
93

            
94
118209
        if !self.check_contents || self.mistrust.is_disabled() {
95
113818
            return boxed(std::iter::empty());
96
4391
        }
97
4391

            
98
4391
        boxed(
99
4391
            walkdir::WalkDir::new(path)
100
4391
                .follow_links(false)
101
4391
                .min_depth(1)
102
4391
                .into_iter()
103
4394
                .flat_map(move |ent| match ent {
104
                    Err(err) => vec![Error::Listing(Arc::new(err))],
105
4
                    Ok(ent) => match ent.metadata() {
106
4
                        Ok(meta) => self
107
4
                            .check_one(ent.path(), PathType::Content, &meta)
108
4
                            .into_iter()
109
4
                            .map(|e| Error::Content(Box::new(e)))
110
4
                            .collect(),
111
                        Err(err) => vec![Error::Listing(Arc::new(err))],
112
                    },
113
4394
                }),
114
4391
        )
115
118209
    }
116

            
117
    /// Return an empty iterator.
118
    #[cfg(not(feature = "walkdir"))]
119
    pub(crate) fn check_content_errors(&self, _path: &Path) -> impl Iterator<Item = Error> + '_ {
120
        std::iter::empty()
121
    }
122

            
123
    /// Check a single `path` for conformance with this `Verifier`.
124
    ///
125
    /// Note that this result is only meaningful if all of the _ancestors_ of
126
    /// this path have been checked.  Otherwise, a non-trusted user could change
127
    /// where this path points after it has been checked.
128
    #[must_use]
129
77815
    pub(crate) fn check_one(
130
77815
        &self,
131
77815
        path: &Path,
132
77815
        path_type: PathType,
133
77815
        meta: &Metadata,
134
77815
    ) -> Vec<Error> {
135
77815
        let mut errors = Vec::new();
136
77815

            
137
77815
        self.check_type(path, path_type, meta, &mut errors);
138
77815
        #[cfg(target_family = "unix")]
139
77815
        self.check_permissions(path, path_type, meta, &mut errors);
140
77815
        errors
141
77815
    }
142

            
143
    /// Check whether a given file has the correct type, and push an error into
144
    /// `errors` if not. Other inputs are as for `check_one`.
145
159518
    fn check_type(
146
159518
        &self,
147
159518
        path: &Path,
148
159518
        path_type: PathType,
149
159518
        meta: &Metadata,
150
159518
        errors: &mut Vec<Error>,
151
159518
    ) {
152
159518
        let want_type = match path_type {
153
            PathType::Symlink => {
154
                // There's nothing to check on a symlink encountered _while
155
                // looking up the target_; its permissions and ownership do not
156
                // actually matter.
157
12
                return;
158
            }
159
42243
            PathType::Intermediate => Type::Dir,
160
93653
            PathType::Final => self.enforce_type,
161
23610
            PathType::Content => Type::DirOrFile,
162
        };
163

            
164
159506
        if !want_type.matches(meta.file_type()) {
165
160
            errors.push(Error::BadType(path.into()));
166
159346
        }
167
159518
    }
168

            
169
    /// Check whether a given file has the correct ownership and permissions,
170
    /// and push errors into `errors` if not. Other inputs are as for
171
    /// `check_one`.
172
    ///
173
    /// On iOS, check permissions but assumes the owner is the current user.
174
    #[cfg(target_family = "unix")]
175
77815
    fn check_permissions(
176
77815
        &self,
177
77815
        path: &Path,
178
77815
        path_type: PathType,
179
77815
        meta: &Metadata,
180
77815
        errors: &mut Vec<Error>,
181
77815
    ) {
182
77815
        // We need to check that the owner is trusted, since the owner can
183
77815
        // always change the permissions of the object.  (If we're talking
184
77815
        // about a directory, the owner cah change the permissions and owner
185
77815
        // of anything in the directory.)
186
77815

            
187
77815
        #[cfg(all(
188
77815
            not(target_os = "ios"),
189
77815
            not(target_os = "tvos"),
190
77815
            not(target_os = "android")
191
77815
        ))]
192
77815
        {
193
77815
            let uid = meta.uid();
194
77815
            if uid != 0 && Some(uid) != self.mistrust.trust_user {
195
                errors.push(Error::BadOwner(path.into(), uid));
196
77815
            }
197
        }
198

            
199
        // On Unix-like platforms, symlink permissions are ignored (and usually
200
        // not settable). Theoretically, the symlink owner shouldn't matter, but
201
        // it's less confusing to consistently require the right owner.
202
77815
        if path_type == PathType::Symlink {
203
12
            return;
204
77803
        }
205

            
206
77803
        let mut forbidden_bits = if !self.readable_okay && path_type == PathType::Final {
207
            // If this is the target object, and it must not be readable, then
208
            // we forbid it to be group-rwx and all-rwx.
209
            //
210
            // (We allow _content_ to be globally readable even if readable_okay
211
            // is false, since we check that the Final directory is itself
212
            // unreadable.  This is okay unless the content has hard links: see
213
            // the Limitations section of the crate-level documentation.)
214
8404
            0o077
215
        } else {
216
            // If this is the target object and it may be readable, or if this
217
            // is _any parent directory_ or any content, then we typically
218
            // forbid the group-write and all-write bits.  (Those are the bits
219
            // that would allow non-trusted users to change the object, or
220
            // change things around in a directory.)
221
69399
            if meta.is_dir() && meta.mode() & STICKY_BIT != 0 && path_type == PathType::Intermediate
222
            {
223
                // This is an intermediate directory and this sticky bit is
224
                // set.  Thus, we don't care if it is world-writable or
225
                // group-writable, since only the _owner_  of a file in this
226
                // directory can move or rename it.
227
8780
                0o000
228
            } else {
229
                // It's not a sticky-bit intermediate directory; actually
230
                // forbid 022.
231
60619
                0o022
232
            }
233
        };
234
        // If we trust the GID, then we allow even more bits to be set.
235
        #[cfg(all(
236
            not(target_os = "ios"),
237
            not(target_os = "tvos"),
238
            not(target_os = "android")
239
        ))]
240
77803
        if self.mistrust.trust_group == Some(meta.gid()) {
241
4
            forbidden_bits &= !0o070;
242
77799
        }
243

            
244
        // Both iOS and Android have some directory on the path for application data directory
245
        // which is group writeable. However both system already offer some guarantees regarding
246
        // application data being kept away from other apps.
247
        //
248
        // iOS: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
249
        // > For security purposes, an iOS app’s interactions with the file system are limited
250
        // to the directories inside the app’s sandbox directory
251
        //
252
        // Android: https://developer.android.com/training/data-storage
253
        // > App-specific storage: [...] Use the directories within internal storage to save
254
        // sensitive information that other apps shouldn't access.
255
        #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "android"))]
256
        {
257
            forbidden_bits &= !0o070;
258
        }
259

            
260
77803
        let bad_bits = meta.mode() & forbidden_bits;
261
77803
        if bad_bits != 0 {
262
494
            errors.push(Error::BadPermission(
263
494
                path.into(),
264
494
                meta.mode() & 0o777,
265
494
                bad_bits,
266
494
            ));
267
77309
        }
268
77815
    }
269
}
270

            
271
impl super::Type {
272
    /// Return true if this required type is matched by a given `FileType`
273
    /// object.
274
159506
    fn matches(&self, have_type: FileType) -> bool {
275
159506
        match self {
276
82793
            Type::Dir => have_type.is_dir(),
277
468
            Type::File => have_type.is_file(),
278
76245
            Type::DirOrFile => have_type.is_dir() || have_type.is_file(),
279
            Type::Anything => true,
280
        }
281
159506
    }
282
}