Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webextensions: add a portal for managing WebExtensions native messaging servers #705

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

jhenstridge
Copy link
Contributor

This is an implementation of a slightly modified version of the interface proposed in #655 for starting WebExtensions native messaging servers on behalf of a confined web browser.

The main change was to remove the use of Request objects, as I'm not waiting for a backend impl server to do the work. It might be worth adding that back though to give more flexibility in the future though.

There are some missing features at present:

  1. a functional test for the new portal
  2. add some restrictions on use of the portal. In the issue, the suggestions were either (a) a fixed permission from the package manifest, or (b) present access control dialogs, and record result in permission store. If we go with (a), that will need to be implemented for both flatpaks and snaps.

Fixes #655.

@smcv
Copy link
Collaborator

smcv commented Feb 1, 2022

This looks like an implementation of the easy parts of #655, which is missing the hard parts, most notably a security model: if I'm reading correctly, this gives every Flatpak/Snap app access to all webextensions, and trusts every Flatpak/Snap app to say what domain it is acting on behalf of.

Of course it's useful to have an implementation of the easy parts, but it cannot be merged without someone doing the harder work to design a security model for it, and then implement that security model. I certainly wouldn't want anyone to get the impression that this PR is basically finished and just waiting for reviewers to say yes.

At the risk of stating the obvious, it is not OK for an untrusted app com.example.ProbablyMalware to be able to talk to org.gnome.chrome_gnome_shell and say "yes, I promise I'm acting on behalf of extensions.gnome.org", or for it to be able to talk to a password manager and say "yes, I promise I'm acting on behalf of gmail.com".

@ramcq
Copy link

ramcq commented Feb 1, 2022

The answer to "is this a static permission" or "is this a user prompting issue" is, IMO, both.

A sandboxed browser is the one responsible for checking whether the appropriate sites are or aren't accessing the native messaging hosts they are permitted to. The http/JS/... session can't be moved out of the browser, there's nothing the portal can do to know or verify the origin domain that I can reasonably think of. So the static permission is "I trust this browser to have some internal confinement and be able to identify a site to me correctly" -> "therefore it may speak to the native messaging host portal and identify a site to the user".

The user consent would be specific and timely "[Browser] wants [Website] to access [Application] on your system." and provides the user the choice to verify several things:

  • are they using website, or is this just bogus noise fed by an addled (or compromised) browser or weird extension
  • do they want it to access the given application
  • do they want it currently...

@jhenstridge
Copy link
Contributor Author

I agree that the access control side needs to be completed: that's why I marked the PR as draft to indicate that it wasn't ready to be merged. I published this more so there would be something to get feedback on the API and be able to test if it was fit for purpose.

As for the permission dialog, the pieces of information we have for a prompt dialog are:

  1. the XdpAppInfo information about the caller (which can give us a desktop file with localised app name and icon)
  2. the requested native server name (e.g. a string like org.gnome.chrome_gnome_shell)
  3. the extension or origin ID (e.g. a string like [email protected] or chrome-extension://gphhapmejobijbbhgpjhcjognlahblep/)
  4. the description field from the native server's metadata file (e.g. "Native connector for extensions.gnome.org")

I think (1) and (4) would be the most important pieces of information to include in the access dialog. The extension ID is not likely to be useful to the user (especially in the Chromium case, or Firefox extensions still using UUIDs as their IDs). If we want to provide a human readable name for the extension, then the browser would need to provide that to the API.

That said, I'm not sure whether it makes sense to include information about the extension in the access dialog, since it is effectively self asserted data from an untrusted client: we're simply using it in the portal implementation to help the browser implement its own security policies within the app sandbox.

@jhenstridge
Copy link
Contributor Author

Also, in follow-up to #705 (comment), the native messaging host is invoked by a browser extension rather than a web page. So in the case of the chrome-gnome-shell native messaging server, it isn't the extensions.gnome.org website talking to it directly: rather it talks to an extension that in turn invokes the native messaging server.

So I don't think it makes sense to include website details in the access dialog either.

@ramcq
Copy link

ramcq commented Feb 2, 2022

That said, I'm not sure whether it makes sense to include information about the extension in the access dialog, since it is effectively self asserted data from an untrusted client: we're simply using it in the portal implementation to help the browser implement its own security policies within the app sandbox.

I'm not sure this is quite right. The native hosts are installed on the host system, or if they are exported via a sandboxing runtime such as Flatpak or Snap, the metadata provided around naming (or icons, etc) is at an equivalent trust level to eg, the names/icons in a .desktop file, which the portal already relies on heavily (ie via XdpAppInfo) for user decisions around access/launching/etc.

(Of course, whether Flatpak should be exporting app icons and descriptions unmodified is a separate question, the security model there seems to be about filtering by app ID, which is not what the user sees, but that's not a regression introduced by this PR so I wouldn't get bogged down in it. If we look at eg exporting native message hosts from a Flatpak, this would need some attention.)

Also, in follow-up to #705 (comment), the native messaging host is invoked by a browser extension rather than a web page. So in the case of the chrome-gnome-shell native messaging server, it isn't the extensions.gnome.org website talking to it directly: rather it talks to an extension that in turn invokes the native messaging server.

Oh yes. That's a bit less useful indeed. Is there a way we can allow the browser (ie trusted enough to have the static permission) to provide optional additional info here that identifies the extension in a slightly better way than the ID? Presumably our fallback implementation of this is a stub launcher that just calls to the proxy, and the sandboxed browser is unaware that there's any sandbox exit going on (I got chrome-gnome-shell working once on this basis) but if we move to submitting patches we might be able to do better?

@swick
Copy link
Contributor

swick commented Feb 3, 2022

If browsers have static permission to allow access to native messaging servers the native messaging servers already only have to deal with trusted parties (i.e. everyone talking to a native messaging server is authenticated as a browser). Similarly if flatpak only allows to export native messaging servers with the name matching the appid, the browsers only deal with authenticated parties.

Any kind of dynamic permission dialog in the portal can only show the appid of the browser and the appid/name of the native messaging server which is completely redundant. We can't show any relevant information in the portal because we don't understand the mechanisms of the native messaging server.

Instead the browser can (and probably should) ask the user if it's okay for a specific add-on to talk to a specific app/native messaging server. (If we didn't have static permissions for browsers it would be the responsibility of the native messaging server to ask the user if it want's to allow a specific request from a specific browser/app.)

@refi64
Copy link
Contributor

refi64 commented Feb 23, 2022

I feel like this is leaving out an important aspect of native messaging hosts, which would be the browser communicating with other Flatpaks (e.g. Chrome talking to 1password). That's technically already possible & I have some PoC code locally in that direction, but if there's going to be a full portal, it probably needs to handle that case too to have a unified implementation.

@jhenstridge jhenstridge marked this pull request as ready for review March 4, 2022 11:19
@jhenstridge jhenstridge force-pushed the native-messaging-portal branch from a21ad86 to 21113e2 Compare March 4, 2022 11:29
@jhenstridge
Copy link
Contributor Author

I've updated the PR to add the suggested permission checks: both a static check in CreateSession (currently passes for everything: will need appropriate implementations for Flatpaks and Snaps), and an access dialog check in Start. This involved switching Start over to returning its result asynchronously through a Request object, so we don't have the method reply waiting on user input.

The text in the access dialog could use some work, as I'm not sure how best to describe the native messaging server in a way that would be meaningful to the user. If it's decided that a static permission is sufficient, then I guess we can remove this check and this wouldn't matter.

@oSoMoN
Copy link

oSoMoN commented Mar 4, 2022

It would be useful to add a stderr out parameter to GetPipes(), because browsers log the native app's standard error stream to their console.

@oSoMoN
Copy link

oSoMoN commented Mar 8, 2022

I'm playing with this, and I noticed that if I manually kill the spawned process, the portal crashes with the following message:

g_mutex_clear() called on uninitialised or locked mutex

@oSoMoN
Copy link

oSoMoN commented Mar 11, 2022

An update on my work to integrate this in Firefox: I've submitted a WIP patch that's mostly functional, but I still need to figure out why reading/writing from/to the passed file descriptors fails, Firefox complains that the files are closed.

@oSoMoN
Copy link

oSoMoN commented Mar 18, 2022

And another update: I figured out the file descriptors issue, so this is now working as expected, in my limited testing. I am now building a snap with this patch to test under proper confinement.

@oSoMoN
Copy link

oSoMoN commented Mar 24, 2022

A note for my future self: when testing this against e.g. the firefox snap and the chrome-gnome-shell native connector, the following command is useful to grant the required permission:

flatpak permission-set webextensions org.gnome.chrome_gnome_shell snap.firefox yes

src/webextensions.c Outdated Show resolved Hide resolved
src/webextensions.c Outdated Show resolved Hide resolved
Copy link

@alexmurray alexmurray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jhenstridge can you elaborate a bit more on the TODO around checking whether the requesting app is a browser in xdp_app_info_is_web_browser()?

src/xdp-utils.c Outdated Show resolved Hide resolved
hosts_path_str = g_getenv ("XDG_DESKTOP_PORTAL_WEB_EXTENSIONS_PATH");
if (hosts_path_str == NULL)
{
hosts_path_str = "/usr/lib/mozilla/native-messaging-hosts:/etc/opt/chrome/native-messaging-hosts:/etc/chromium/native-messaging-hosts";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also include /etc/opt/edge/native-messaging-hosts for Microsoft Edge?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure about this one. If we have any messaging hosts supporting Edge, would the json files they install for Chrome likely cover the IDs of the Edge extension too?

@oSoMoN
Copy link

oSoMoN commented May 11, 2022

Thanks for merging the fix for the access dialog.

In my testing, I'm still consistently seeing the following problem: the first time the user is prompted for authorization and accepts, the portal will spawn the native connector and pass the file descriptors to the client (firefox snap in my tests), but the client sees the file descriptors as closed. Closing the client and re-opening it doesn't help. Only when the portal is terminated and restarted does communication through FDs start working normally.
I am therefore suspecting a bug in the portal itself, but I haven't been able to put my finger on the exact problem yet.

@oSoMoN
Copy link

oSoMoN commented May 19, 2022

In my testing, I'm still consistently seeing the following problem: the first time the user is prompted for authorization and accepts, the portal will spawn the native connector and pass the file descriptors to the client (firefox snap in my tests), but the client sees the file descriptors as closed. Closing the client and re-opening it doesn't help. Only when the portal is terminated and restarted does communication through FDs start working normally. I am therefore suspecting a bug in the portal itself, but I haven't been able to put my finger on the exact problem yet.

And with the following patch to grant authorization right away instead of prompting the user, the problem goes away:

--- a/src/webextensions.c
+++ b/src/webextensions.c
@@ -392,7 +392,7 @@ handle_start_in_thread (GTask *task,
     }
 
   app_id = xdp_app_info_get_id (request->app_info);
-  permission = get_permission_sync (app_id, PERMISSION_TABLE, arg_name);
+  permission = PERMISSION_YES;
   if (permission == PERMISSION_ASK || permission == PERMISSION_UNSET)
     {
       guint access_response = 2;

src/webextensions.c Outdated Show resolved Hide resolved
jhenstridge and others added 25 commits January 13, 2025 11:42
…ng servers.

This is intended to provide a way for a confined web browser to start
native code helpers for their extensions. At present it can start the
servers installed on the host system. But in future this could be
extended to cover sandboxed native messaging servers too.
…n the wrong state.

This could occur if the session is closed between when the task is
scheduled and when it is run.
This ensures the session won't be freed while the callback is running,
as could happen when close_sessions_in_thread_func() runs.  While this
creates a reference cycle, it will be broken when the session is closed.
Firefox also documents /usr/lib64/mozilla/native-messaging-hosts as part
of the search path. Also include locations based on xdg-desktop-portal's
install location.
…ession.

At this point, the browser has signalled the native messaging host to
exit by closing its standard input. If the process is still running when
the browser closes the session, then it is not responding.
…ther to provide Chromium-ish or Mozilla-ish behaviour

This affects the following behaviour:

1. the search path for manifest files.
2. whether to match the "allowed_origins" or "allowed_extensions" key.
3. the command line arguments passed to the native messaging server.

The tests have also been updated to use a shell script as the test
native messaging server. Previously we just used /bin/cat, since it
would stick around until its standard input was closed. This broke once
we started passing arguments to the server. So now use a shell script
that calls cat with no arguments.
While Chromium based browsers are more common, we only have an
implementation for Firefox for now, which is not setting this option.
To help folks looking to work on the Web Extensions portal without
prior familiarity with the XDG Desktop Portal and related code-bases
like one of its main dependencies GLib and its object system.
Explain the session closure behaviour from browsers' perspective
and link to the document about Firefox's implementation.

Also, add references about arguments passed to the native executable
in the Mozilla/Firefox and Chromium modes.
… manifest search path

https://bugzilla.mozilla.org/show_bug.cgi?id=259356

Also add a comment in get_manifest_search_path about the importance
and explanation of why the searched locations must be inaccessible to
the sandboxed browsers.
@jhenstridge jhenstridge force-pushed the native-messaging-portal branch from 9c0fc43 to 21e3a9a Compare January 13, 2025 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new portals This requires creating a new portal interface
Projects
No open projects
Status: Triaged
Status: No status
Development

Successfully merging this pull request may close these issues.

NativeMessaging portal for sandboxed browsers