fix: refuse to connect an agent from a translocated macOS app
A `.dmg` downloaded from the internet keeps its quarantine bit, so Gatekeeper may run the app from a read-only copy under `/private/var/folders/…/AppTranslocation/…` that vanishes when the app closes. Connected from there, an agent would be pointed at a path that will not exist the next time it runs. Connect refuses instead, before a token is made or an agent's list is touched, and says to move the app out of Downloads and open it again (P11a step 6). The rule is on the path the program would give the agent (`start::translocation_refusal` over `this_executable`), so the same check covers the CLI's `connect` and the viewer's Connect. The test stands in for Gatekeeper's copy on any Unix by naming a file under an `AppTranslocation` path in `APPIMAGE`; the agent never runs and no books home is made, while the same program at an ordinary path connects. A unit test holds the marker to a whole path part, not letters in a name.
This commit is contained in:
1 parent
53a233d687
commit
e2754898d8
3 files changed
+176
-1
No files matched your search
@@ -1262,6 +1262,12 @@ pub fn connect_agent(common: &Common, agent: &str, book: &str) -> Result<Vec<Str
|
||||
"{label} isn't installed here (no `{cli}` on the PATH, nor where its installer puts it)"
|
||||
)
|
||||
})?;
|
||||
// A translocated macOS app's path (P11a step 6) is not a place to point
|
||||
// an agent: the copy goes away with the app.
|
||||
let exe = crate::start::this_executable()?;
|
||||
if let Some(why) = crate::start::translocation_refusal(&exe) {
|
||||
return Err(why.to_string());
|
||||
}
|
||||
let mut lines = Vec::new();
|
||||
// The token: kept in its file, or made now.
|
||||
let found =
|
||||
@@ -1279,7 +1285,6 @@ pub fn connect_agent(common: &Common, agent: &str, book: &str) -> Result<Vec<Str
|
||||
));
|
||||
}
|
||||
// The agent's own list: the books' entry made anew.
|
||||
let exe = crate::start::this_executable()?;
|
||||
let name = format!("wanderbooks-{book}");
|
||||
let mut serve: Vec<std::ffi::OsString> = vec![
|
||||
exe.into_os_string(),
|
||||
|
||||
@@ -83,6 +83,21 @@ pub fn this_executable() -> Result<PathBuf, String> {
|
||||
Ok(exe)
|
||||
}
|
||||
|
||||
/// Why this program can't point an agent at itself, if it can't (P11a
|
||||
/// step 6): macOS runs a quarantined app from a read-only copy under
|
||||
/// `/private/var/folders/…/AppTranslocation/…`, a path that goes away when
|
||||
/// the app closes. The app may run, but an agent connected from it would
|
||||
/// lose the books, so the person moves it out of Downloads first.
|
||||
/// `exe` is the path this program would give the agent
|
||||
/// ([`this_executable`]).
|
||||
pub fn translocation_refusal(exe: &Path) -> Option<&'static str> {
|
||||
exe.components()
|
||||
.any(|part| part.as_os_str() == "AppTranslocation")
|
||||
.then_some(
|
||||
"wanderbooks is running from the copy macOS makes of a quarantined download, and that copy goes away when the app closes: move wanderbooks out of Downloads (Applications, for example), open it there, and connect the agent again",
|
||||
)
|
||||
}
|
||||
|
||||
/// The AppImage this process runs from: `APPIMAGE` when it is an
|
||||
/// absolute path to a file.
|
||||
pub fn own_appimage(value: Option<&std::ffi::OsStr>) -> Option<std::ffi::OsString> {
|
||||
@@ -633,6 +648,28 @@ mod tests {
|
||||
let exe = this_executable().unwrap();
|
||||
assert!(exe.is_file(), "{exe:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_translocated_path_refuses_an_agent() {
|
||||
assert_eq!(
|
||||
translocation_refusal(Path::new(
|
||||
"/Applications/Wanderbooks.app/Contents/MacOS/wanderbooks"
|
||||
)),
|
||||
None
|
||||
);
|
||||
let copy = translocation_refusal(Path::new(
|
||||
"/private/var/folders/xy/T/AppTranslocation/1234/d/Wanderbooks.app/Contents/MacOS/wanderbooks",
|
||||
))
|
||||
.expect("a translocated path is refused");
|
||||
assert!(copy.contains("out of Downloads"), "{copy}");
|
||||
// A part that only shares letters is not the marker macOS makes.
|
||||
assert_eq!(
|
||||
translocation_refusal(Path::new(
|
||||
"/Applications/AppTranslocation Notes/wanderbooks"
|
||||
)),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows))]
|
||||
|
||||
Reference in new issue
Block a user