-
Notifications
You must be signed in to change notification settings - Fork 17.3k
Fix buggy partial startup on macOS due to flag added by Gatekeeper/XProtect #21861
Fix buggy partial startup on macOS due to flag added by Gatekeeper/XProtect #21861
Conversation
We don't need this flag, and yargs thinks it is many short flags.
So, we filter it out before it causes too much trouble.
("Trouble" meaning TypeErrors, partial startup, no windows opened...)
---
Background info:
When launching Atom on macOS, if Gatekeeper/XProtect intercepts the
app launch, and the user clicks through/if launching continues,
Gatekeeper/XProtect add a flag of the form: `-psn_0_[1234567]`.
(The six or seven digits at the end
are apparently a "Process Serial Number",
which the internets say dates back to the old "Carbon" framework...)
We should filter this out at some point, as it's not desirable input.
Ideally, we should filter it out before it gets to yargs.
(Otherwise yargs will see it as twelve or thirteen short flags:
`-p`, `-s`, `-n`, `-_`, `-0`, `-_` (again), `-1`...
which is a lot messier and harder to sort out than a single string.)
---
Fixes: atom#21013 (probably)
Fixes: atom#21751
Fixes: atom#21820
(This flag gets added by Gatekeeper/XProtect sometimes, but it's not wanted, and it trips up yargs, so we filter it out).
The parseCommandLine() function only accepts an array now.
This is an additional workaround for potential arbitrary flags starting with `-` and including an underscore `_`. The idea is to keep starting up with whatever valid inputs were given, rather than quietly fail to initialize after getting a TypeError.
|
@sadick254 and @Aerijo this is another bugfix PR for macOS. Please take a look if you have time. I am confident this works well, but as for the particular approach, I would be interested in any feedback for this implementation and feedback on the tests I added. For example: I added somee of the filtering in one function, but there is a different function I didn't update that handles these inputs if the Also, converting number arguments (for example: As mentioned in the body of the PR, this PR includes two similar and largely redundant fixes, but as I also point out this hasn't resulted in slowdowns, and I think safer, more-cautious code during startup may be worth it. That said, I would understand a "Keep It Simple" approach and so I wonder if just one of these solutions is adequate. |
|
Also I understand if looking deep into this bugfix PR isn't terribly interesting. I tested this implementation a lot, and it works, and it's fast. So, no worries if not interested in giving in-depth feedback for this admittedly simple bugfix. |
I was interested in how VS Code handles And I would like to see the positional arguments always be treated as strings, but that can go in a different PR. |
| assert.deepEqual(args.pathsToOpen, ['/some/path']); | ||
| }); | ||
|
|
||
| describe('and --_ or -_ are passed', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why single out and ignore underscore flags? I guess it's related to -psn_... containing an underscore? Is there a drawback to just allowing (and ignoring) it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing -_ or --_, as either the last argument, or "without a non-flag argument immediately after", is the minimal reproducer for the Gatekeeper partial startup bug.
Maybe I should add this as a code comment in the test file? Or incorporate that into the test's describe/it text?
yargs stores non-flag arguments in an array called argv._, but this can easily be polluted with the content of actual underscore _ flags. (See Gatekeeper's -psn_0_[1234567] as a prime example). And yargs treats any flag with "empty content" (no non-flag argument passed right after the flag) as boolean trues.
Passing the "underscore flag" is the main way to get stray boolean trues into the non-flag argument pool. It's an easy and convenient way to test the Gatekeeper flag bug more generally than a flag starting with -psn_.
Restated in terms relative to the normal non-bug behavior: This test handles boolean pollution in the non-flag argument array, which is the one where we look for paths and URIs that Atom should open.
I thought of it as a natural counterpart to the "number pollution" test right below it.
Huge long reply, since I'm not totally sure what else you may need clarifying, and I was kind of on a roll typing this out. And it's late where I am. (click to expand if there's anything else you were wondering, I may have answered it here):
(Before I get into it: Admittedly none of the following complicated information would make sense from just looking at the test as I wrote it. So maybe I should add comments in the test file. Or make the purpose of the test more obvious from the describe and it text. TODO: make the test self-explanatory.)
Also to address just this question directly:
Why single out and ignore underscore flags? I guess it's related to -psn_... containing an underscore? Is there a drawback to just allowing (and ignoring) it?
I felt that it was just a matter of time before we saw a similarly-formed flag. If it didn't happen to start with exactly -psn_, then we wouldn't be filtering it out, and then we're back to experiencing essentially this same issue.
The general solution of handling/ignoring shortflag sequences containing effectively -_... is very simple. It happens to also filter out --_. I may have gone overboard in explicitly testing for both, but yargs treats them the same under the hood right now, the solution to handle all of this comprehensively is a few lines of code, and I just wanted to be thorough. I figured being mildly paranoid about it now would make our code and test coverage robust to whatever strange inputs may come, and alert us to whatever undocumented minor behavior changes may happen in yargs, for instance.
So, the reason I went there at all is a bit of a rabbit-hole of how yargs works. Well, it seems like a rabbit-hole if you don't know where it ends up. I will try to make this concise but complete.
Background info: what's the normal, desired behavior?
Starting with the normal, desired behavior: yargs stores all non-flag arguments in an array called argv._.
So, Atom (rather reasonably) expects non-flag arguments to be filenames or URLs we want to open. For example:
Running atom file1 ./file2 /path/to/file3 will launch Atom with tabs open for these files.
Quirks in yargs
A few things make it complicated:
- Users and the OS can specify a literal
_flag of either the short form-_or--_, andyargswill dump it right into the sameargv._array with the "non-flag" arguments. yargsby default treats any flag with no (non-flag) argument after it as booleantrue- Bonus complication that's not strictly a part of the bug I'm trying to fix: Any bare number passed as a non-flag argument, say
33, will be turned into a JSnumber. (There is a feature to treat everything inyargs.argv._as strings, but it didn't work for numbers in my testing. It didn't coerce numbers to strings in my testing, it left them as JSnumbertypes. I think this is a bug inyargs).
Side note: (yargs seems to follow a Linux-style convention about shortflags. It's a conventional behavior on Linux to treat a string of characters after a single dash as being a number of shortflags. For example, a typical tar invocation: tar -xvf my_tarball.tgz; in English that's "tar, extract verbosely a file named my_tarball.tgz.")
The purpose of that test (filtering out booleans)
The purpose of that test is to test an array of inputs that has been polluted with booleans. This is the scenario that the -psn_0_[1234567] flag presents.
Passing -_ or --_, without a non-flag argument immediately after, is the minimal reproducer for the Gatekeeper partial startup bug.
Why I didn't just pass booleans into the parser function
Passing non-strings to this function doesn't resemble its real-world use. The function is only used in one place. Here in src/main-process/start.js. Atom always, 100% of the time, passes the parseCommandLine() function either an empty array, or an array of strings. These are read from Node's process.argv immediately before passing to the parseCommandLine() function. Node's process.argv will only ever contain strings, apparently. The diverse typing come in only after yargs has taken a look at the inputs and decided whether they are strings, numbers, booleans...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I think a short comment on that test to explain why would be useful, even if it's just a link to the conversation in this PR.
(turns out yargs has an open bug related to this too yargs/yargs#878)
I suppose total and strict coercion to strings (and ignoring/filtering out the things that shouldn't become strings) would be adequate. I found that surprisingly involved/difficult to do, based on how Particularly, the option to treat everything as strings seemed to miss bare numbers (they stayed I didn't use this feature as it added a line of code and some processing time in the |
This comment has been minimized.
This comment has been minimized.
|
Thinking of how VSCode doesn't specifically handle this flag: The supposed disadvantage of that for us would be letting all the psuedo-random number shortflags through. ( We don't use any of these for anything right now. But I'm thinking ahead that we might want to use them for something in the future? That's a hypothetical concern as of the moment though. In actuality there's not much real-world-relevant justification I can give for specifically and specially handling that flag. (It's fast/practically free performance-wise. So there's not really any harm either.) I'm running out of things to discuss on this one, so I think it might be ready. |
|
If your comparing it to VS CODE, you might as well just |
|
It's more food for thought/sanity check, two heads are better than one, etc. Considering the reasonable alternatives. But I sense you may not be entirely literally serious in your comment. |
|
Test failure is unrelated. It's the timeout on the pane-spec test on the Windows renderer-1 tests job again. I've seen that on a number of unrelated branches. |
|
Here's the other function that processes the array meant to contain paths and URIs. Just like the function I did add protections to, this one also absolutely expects its inputs to be strings (or it gives I left this function alone and didn't add any protections to it, because I wanted a small diff and to keep the parser simple and readable, and because in my testing, this flag is only organically used on Windows, so the offending macOS-only bug is not applicable. Miscellaneous notes on this (click to expand):
(That's the last topic I can think of to discuss here. Recapping some previous comments I posted: I'm on the fence about whether to remove the specific filter for |
|
@sadick254 Thank you for merging! |
…rotect (atom#21861) macOS Gatekeeper adds a flag ("-psn_0_[six or seven digits here]") when it intercepts Atom launches. This happens for fresh downloads, new installs, or first launches after upgrading). We don't need this flag, and yargs interprets it as many short flags. So, we filter it out. (cherry picked from commit 7928a1b)
…rotect (atom#21861) macOS Gatekeeper adds a flag ("-psn_0_[six or seven digits here]") when it intercepts Atom launches. This happens for fresh downloads, new installs, or first launches after upgrading). We don't need this flag, and yargs interprets it as many short flags. So, we filter it out. (cherry picked from commit 7928a1b)
* 🍎 Don't quit upon 'window-all-closed' event on macOS (#21847) * 🍎 Don't quit on 'window-all-closed' on macOS This prevents a lot of buggy partial-quitting stuff from happening. Fixes: #17672 Fixes: #20831 (cherry picked from commit 72709dc) Co-authored-by: Benjamin Gray <[email protected]> * Fix buggy partial startup on macOS due to flag added by Gatekeeper/XProtect (#21861) macOS Gatekeeper adds a flag ("-psn_0_[six or seven digits here]") when it intercepts Atom launches. This happens for fresh downloads, new installs, or first launches after upgrading). We don't need this flag, and yargs interprets it as many short flags. So, we filter it out. (cherry picked from commit 7928a1b) Co-authored-by: Benjamin Gray <[email protected]>
Requirements for Contributing a Bug Fix (from template, click to expand)
Identify the Bug
Deeper technical explanation (click to expand)
(Paraphrased from: #21751 (comment))
[This bug appears] related to Gatekeeper.
[This] bug happens [when you] are launching Atom and see Gatekeeper pop up with a loading bar and a message 'Verifying "Atom"', or something about "This was downloaded from the internet, are you sure you want to open this?". This bug happens when Gatekeeper [intervenes in] launching the app [but the user clicks though/the app is verified and launching continues].
I see a command-line flag
-psn_0_[some six or seven digits here](something like-psn_0_1234567) applied to the buggy Atom process, when inspecting usinghtoporps -ax. I have seen reports elsewhere that this flag is added by Gatekeeper. For example: godotengine/godot#37719Our argument parser library
yargsthinks this-psn_0_1234567flag is a bunch of short flags, equivalent to-p,-s,-n,-_,-0,-_,-1,-2,-3,-4,-5,-6,-7. The-_are converted to booleantrue, and then we try to do JS string-only operations on the flags. Doingtrue.startsWith()results in a TypeError ("path.startsWithis not a function") very early in our initialization process, leaving the app barely started up at all, and unable to respond to most kinds of input other than quitting.Quitting and restarting means Gatekeeper is already familiar with the binary, and it won't intervene with launching the app. So the flag doesn't get added, and Atom works fine after a restart.
Fixes: #21013 (probably)
Fixes: #21751
Fixes: #21820
Description of the Change
-psn_0_[1234567]) that macOS Gatekeeper/XProtect adds to Atom. Filter it out beforeyargstries to parse it.-psn_.yargsthinks this is many shortflags (-p-s-n-_-0-_...); Parsing and handling all of them without it causing trouble is somewhat difficult. Filtering it out early avoids all the complications and bugs (TypeErrors, partial startups).)args._array, in one of the functions that decides whether an argument is a path or a URI.Alternate Designs
We could use just one of these two fixes instead of both. I included both to be thorough and handle more potential situations more correctly.
Miscellaneous other proposals (click to expand):
I also considered converting number arguments to strings, so users can easily open a file like
55or100in the current directory (no file extension or relative/absolute path needed). But I left that out because it felt like it was veering off-topic for a bug-fix. (yargshas an option that supposedly would do that for us, but it doesn't help very much in my testing; we would still need to filter out the zero-length strings it converts booleantrueto with that option, and we would still need to manually convert numbers to strings, because that part doesn't work. I think that's a bug inyargs.)Possible Drawbacks
None.
(I expected tiny slowdowns, but the relevant tests run at the same ~16-18ms speed on this branch as
masterbranch on my machine.)Verification Process
Added tests to describe new expected behavior. Tests pass.
macOS Gatekeeper flag no-longer prevents Atom from completely starting up.
To reproduce the problem, run this in the Terminal app:
atom "-psn_0_1234567". You should see the partial startup bug as described in the issues (linked at the top of this PR).Try again on this branch to confirm the bug goes away; For example, using Atom in dev mode:
Release Notes
Fixed buggy partial startup on macOS due to flag added by Gatekeeper/XProtect