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 23 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 chrome-gnome-shell@gnome.org 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
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 14 commits February 13, 2024 11:42
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.
@sudoshindo
Copy link

Progress!!! 😮😭

}

static void
web_extensions_session_class_init (WebExtensionsSessionClass *klass)
Copy link

Choose a reason for hiding this comment

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

(Question to myself during review)

Where is web_extensions_session_class_init used? Similarly for web_extensions_class_init.

Upon some searching around, I found that the code pattern here resembles https://docs.gtk.org/gobject/concepts.html#object-instantiation

and that there is a line elsewhere in this file with: G_DEFINE_TYPE (WebExtensionsSession, web_extensions_session, session_get_type ());

...and that an example of equivalent code without macros is shown at: https://docs.gtk.org/gobject/concepts.html#instantiatable-classed-types-objects

and that there is some documentation around when the finalize functions are invoked, at: https://docs.gtk.org/gobject/concepts.html#reference-count

P.S. Given my unfamiliarity with these conventions, I am assuming that someone else has performed a review of these aspects. I am only focusing on issues that require domain knowledge of the WebExtensions / native messaging / Firefox / Chrome aspects of this.

Choose a reason for hiding this comment

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

Sorry for the delay, and ack, I've added some comments documenting the flow of execution a bit as I understood it. I'll be addressing more of your comments soon as well.

object_class->finalize = web_extensions_session_finalize;

session_class = (SessionClass *)klass;
session_class->close = web_extensions_session_close;
Copy link

Choose a reason for hiding this comment

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

So this sets up the handler of org.freedesktop.portal.Session::Close.

Would be nice to have a comment, so that it is easier to grep for the handler that was mentioned in data/org.freedesktop.portal.WebExtensions.xml

Suggested change
session_class->close = web_extensions_session_close;
// Register handler for org.freedesktop.portal.Session::Close
session_class->close = web_extensions_session_close;

Copy link

Choose a reason for hiding this comment

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

Done.

web_extensions_session_close (Session *session)
{
WebExtensionsSession *web_extensions_session = (WebExtensionsSession *)session;

Copy link

Choose a reason for hiding this comment

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

Add comment to clarify.

Suggested change
// This function can be called repeatedly, e.g. by an explicit
// "org.freedesktop.portal.Session::Close" message followed
// by a call to finalize due to session's refcount reaching zero.

Copy link

Choose a reason for hiding this comment

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

Done.

g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Don't allow")));
g_variant_builder_add (&opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow")));
if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl,
Copy link

Choose a reason for hiding this comment

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

I previously posted feedback at #705 (comment) that did not get an answer. I've tried to find an answer, below. Please take a look and verify/challenge my answer.

It may take a while before the permission is granted and/or launch, during which the initiator of the session could already be gone. E.g. in any of the following situations:

  • The extension has explicitly closed the port.
  • The page within the extension has closed.
  • The extension was disabled or unloaded.
  • The browser quit.

Does the current implementation guarantee that (1) the session is closed and (2) the native app is killed as expected?

The current function is handle_start_in_thread, which calls xdp_dbus_impl_access_call_access_dialog_sync. This seems to block the thread until the user has approved or denied the request for a browser extension to connect through the portal.

In all situations from my bullet list, org.freedesktop.portal.Session::Close is called, unless the browser was interrupted/terminated before it could gracefully end the session. The implementation in this PR seems to rely on web_extensions_session_finalize to clean up the session if al references got lost. Since SESSION_AUTOLOCK_UNREF (g_object_ref (session)); is used above, I presume that the refcount will never drop to zero as long as we are in this class.

So my conclusion is:

  • if the browser crashes or is killed while the prompt is shown, then the implementation here does not get notified. After the prompt is approved, the native messaging host app is launched a few lines below, and then when the refcount drops to zero, the native app is immediately killed.
  • if the browser closes through org.freedesktop.portal.Session::Close, then web_extensions_session_close is called. Does this call await the completion of this handle_start_in_thread call? Are they even called on the same thread? (I suspect that the answer to both is no) If the two run sequentially, then the cleanup follows the same flow as in the previous bullet point. If they do run in parallel, then we'd end up with a native messaging host that outlives the caller (browser extension inside web browser). That is bad.

In the current implementation, Start can take an arbitrary amount of time. Even if the calling browser has shut down a long time ago, it is technically possible for the prompt to be somewhere on screen, and trigger the launch of the native messaging host application (followed by an attempt to quit it). This is acceptable from the browser's perspective, but is it too from the portal's perspective? Please add ample comments/documentation somewhere!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As things currently stand, handle_start_in_thread is holding the session object's mutex when it pops up the access dialog. So a call to the Close method would block when it tried to acquire the mutex. The same would be true if the browser exited and we were trying to clean up.

What does seem to be missing is that we aren't passing the cancellable to call_access_dialog_sync. This would let the browser withdraw the access dialog early by calling Close on the Request object returned by Start, and ensure the cleanup (in close_requests_in_thread_func) doesn't block should the browser exit.

Copy link

Choose a reason for hiding this comment

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

What does seem to be missing is that we aren't passing the cancellable to call_access_dialog_sync.

The cancellable is now passed in c739835 .

This would let the browser withdraw the access dialog early by calling Close on the Request object returned by Start, and ensure the cleanup (in close_requests_in_thread_func) doesn't block should the browser exit.

This is currently not implemented on the browser side, I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1903338 for that.

Is the session automatically canceled (and the prompt dismissed) if the session is closed (e.g. due to the browser shutting down)? Could we have a unit test for that?


if (web_extensions_session->state == WEB_EXTENSIONS_SESSION_STATE_CLOSED) return;

web_extensions_session->state = WEB_EXTENSIONS_SESSION_STATE_CLOSED;
Copy link

Choose a reason for hiding this comment

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

Is it safe to immediately transition web_extensions_session->state to the WEB_EXTENSIONS_SESSION_STATE_CLOSED state?

If this web_extensions_session_close function does not run on the same thread as handle_start_in_thread, then trouble happens when we that function is being executed (and state is WEB_EXTENSIONS_SESSION_STATE_STARTING):

  • Data races, e.g. concurrent write to web_extensions_session->state.
  • The native messaging host application launches and is never terminated. It can outlive the browser process that requested the session.

To avoid this issue, you need to synchronize access to the members touched here. And/or have checkpoint(s) in handle_start_in_thread that avoids the process launch or quits the launches process if the session was closed.

If possible, please add a unit test where the caller tries to close the session when the program is still being launched and verify that reasonable behavior happens (i.e. program exits when the caller is no longer there, no crashes, try compiling with TSan to check for thread data races).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's two places this function is called:

  1. the session_close() function in src/session.c, via the session_class->close function pointer. This function expects the caller to lock the session's mutex (usually via the SESSION_AUTOLOCK_UNREF macro).
  2. in web_extensions_session_finalize, at which point the last reference to the session has been released.

Choose a reason for hiding this comment

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

James's clarifications/explanations as to why it's safe to do this were added as comments in the code.


if (web_extensions_session->child_pid > 0)
{
kill (web_extensions_session->child_pid, SIGKILL);
Copy link

Choose a reason for hiding this comment

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

Unconditional SIGKILL is not the way to go. For a description the correct termination sequence, see #705 (comment)

If you need inspiration on how to implement a waitpid-with-timeout, here is an example: https://source.chromium.org/chromium/chromium/src/+/main:base/process/process_posix.cc;l=36;drc=f4a00cc248dd2dc8ec8759fb51620d47b5114090

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in the previous comment threads, xdg-desktop-portal can only implement part of the shutdown process. We do not hold on to copies of the process's stdin/stdout file descriptors, since that would prevent the browser from signalling the process to terminate by closing its own copies of the file descriptors.

From the browser's perspective, the shutdown process would be:

  1. close the process's stdin/stdout file descriptors obtained from xdg-desktop-portal.
  2. Wait for a D-Bus Closed signal on the org.freedesktop.portal.Session object, which will be triggered on SIGCHLD via the g_child_watch_add_full handler.
  3. If the signal doesn't come in time, call the Close method on the session object.

So the code here is just doing the final SIGKILL, and leaving the timeout policy up to the browser.

Copy link

Choose a reason for hiding this comment

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

@bandali0 What was described above is not implemented in the Firefox-side patch. To see the relevant code, use Ctrl+F "closeSession" at https://phabricator.services.mozilla.com/D140803. Are you going to implement it?

On the portal patch here, please add a comment that explains that the responsibility of graceful killing the application has been delegated to Firefox, and that the SIGKILL here is a last shot attempt to clean up if needed.

P.S. This is the third time I'm posting the comment, because for some reason it ended up in the wrong place when I posted before.

Copy link

Choose a reason for hiding this comment

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

I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1903309 as a follow-up on the Firefox side.

If we want graceful termination, then we'd need a new message in the protocol (to send SIGTERM), don't we?

{
case WEB_EXTENSIONS_SESSION_MODE_CHROMIUM:
/* Chrome and Chromium search paths documented here:
* https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-location
Copy link

Choose a reason for hiding this comment

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

Suggested change
* https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-location
* https://developer.chrome.com/docs/extensions/nativeMessaging/#native-messaging-host-location

Fixed documentation link. "apps" is deprecated in Chrome and should not be referenced.

Copy link

Choose a reason for hiding this comment

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

Done.

Comment on lines 316 to 317
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "opt/chrome/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "chromium/native-messaging-hosts"));
Copy link

Choose a reason for hiding this comment

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

Suggested change
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "opt/chrome/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "chromium/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "/opt/chrome/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "/chromium/native-messaging-hosts"));

You're missing / after SYSCONFDIR. SYSCONFDIR does not end with a /, according to the current uses in the source: https://github.com/search?q=repo%253Aflatpak%252Fxdg-desktop-portal%20SY

Copy link

Choose a reason for hiding this comment

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

Done.

g_ptr_array_add (search_path, g_strdup ("/usr/lib/mozilla/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup ("/usr/lib64/mozilla/native-messaging-hosts"));
/* And the same for xdg-desktop-portal's configured prefix */
g_ptr_array_add (search_path, g_strdup (LIBDIR "mozilla/native-messaging-hosts"));
Copy link

Choose a reason for hiding this comment

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

Suggested change
g_ptr_array_add (search_path, g_strdup (LIBDIR "mozilla/native-messaging-hosts"));
g_ptr_array_add (search_path, g_strdup (LIBDIR "/mozilla/native-messaging-hosts"));

Added slash assuming that it was missing by mistake.

Is there any documentation or reference that supports the use of the LIBDIR compile time constant? Please add documentation somewhere...

Copy link

Choose a reason for hiding this comment

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

Done.

session can at any time be closed using
org.freedesktop.portal.Session::Close, or may at any time be
closed by the portal implementation, which will be signalled
via org.freedesktop.portal.Session::Closed.
Copy link

Choose a reason for hiding this comment

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

Elsewhere in this review (and earlier, at #705 (comment)) I described the expected behavior when a session is closed.

Let's explicitly document what one can expect to happen when the Close is received, and what cause when Closed is signaled. See my comment elsewhere in this review round to get an idea of what you could document.

Choose a reason for hiding this comment

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

Explanatory comment added.

@kevin-tarantino
Copy link

kevin-tarantino commented Apr 7, 2024

  1. the portal uses a single search path for native messaging hosts rather than supporting different paths for different browsers. My main concern here is that xdg-desktop-portal is generally updated on systems far less frequently than the sandboxed applications.
    I would prefer an API where we don't need to deploy a new xdg-desktop-portal each time a browser wants to add support for this portal interface.

If a new browser wants to support the interface, would that not require a change in x-d-p anyway because that browser would have its own manifest search paths? E.g. MyCoolNewBrowser™ wouldn't instruct developers to place their native manifests in /usr/lib/mozilla/native-messaging-hosts/.

  1. the portal does not perform the same graceful shutdown as Firefox does. In this case, the portal cannot do everything Firefox currently does because it does not hold on to the native messaging host file descriptors after the browser has requested them. This was done so that the browser could initiate a graceful shutdown by closing it's copy of the file descriptors.

Does the browser even want to do that itself? What is the problem with just having the browser call Close and letting our side do the full closefds-sigterm-sigkill procedure? This would have the advantages that (i) all the code would be in one place, (ii) the complete procedure could be performed even if the browser dies, and (iii) it would be more elegant/intuitive because if Close actually performed the full shutdown procedure (not just the "fuck it, SIGKILL" part), it would be a more proper counterpart to Start, tearing down what it set up in the best way.

After all, the current portal implementation (used on Ubuntu) allows that usage.

How can this currently be used anywhere in production? I thought the PR is not merged?? There also is confusion elsewhere about this. Can somebody explain?

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.
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location
*/
/* Add per-user directories */
g_ptr_array_add (search_path, g_build_filename (g_get_home_dir (), ".mozilla", "native-messaging-hosts", NULL));
Copy link

Choose a reason for hiding this comment

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

We might want to also handle $XDG_CONFIG_HOME/mozilla here?

Copy link

Choose a reason for hiding this comment

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

@lissyx
Your comment is related to https://bugzilla.mozilla.org/show_bug.cgi?id=1903246 right?

Is there canonical documentation that documents this expected location?

Copy link

Choose a reason for hiding this comment

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

yes, it will be taken care of https://bugzilla.mozilla.org/show_bug.cgi?id=1905711, but i dont think we can update MDN so soon yet without any expected timeline, and i wanted to make sure there was a note of that somewhere

Copy link

Choose a reason for hiding this comment

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

Even if XDG_CONFIG_HOME were to be supported, then developers of native messaging components should still continue to write in the "old" location to support environments that do not support XDG_CONFIG_HOME. Therefore I think that it is acceptable to proceed with the current logic as is, and handle XDG_CONFIG_HOME later.

Do you agree with that assessment?

Copy link

@lissyx lissyx Jul 4, 2024

Choose a reason for hiding this comment

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

Obviously, both will have to work, that's what we are already doing on Firefox side, only new profiles would default to $XDG_CONFIG_HOME use. I did mention « also » at first, i was not planning on replacing the path just having to dig into the code and see how to handle both

Copy link

Choose a reason for hiding this comment

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

Obviously, both will have to work, that's what we are already doing on Firefox side, only new profiles would default to $XDG_CONFIG_HOME use. I did mention « also » at first, i was not planning on replacing the path just having to dig into the code and see how to handle both

I was referring to external application developers, that they can't only support XDG_CONFIG_HOME, and that therefore it may be acceptable for the portal to not recognize XDG_CONFIG_HOME.

The timeline of shipping support for XDG_CONFIG_HOME is unclear to me. If it is "relatively soon", say a few months at most, then I suppose that we can integrate it in the portal right away.

I asked for documentation or a description of the XDG_CONFIG_HOME feature because that would make it more obvious what the desired order of lookup should be.

Copy link

Choose a reason for hiding this comment

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

Obviously, both will have to work, that's what we are already doing on Firefox side, only new profiles would default to $XDG_CONFIG_HOME use. I did mention « also » at first, i was not planning on replacing the path just having to dig into the code and see how to handle both

I was referring to external application developers, that they can't only support XDG_CONFIG_HOME, and that therefore it may be acceptable for the portal to not recognize XDG_CONFIG_HOME.

yes, that's why we try legacy first

The timeline of shipping support for XDG_CONFIG_HOME is unclear to me. If it is "relatively soon", say a few months at most, then I suppose that we can integrate it in the portal right away.

there's no hardcoded deadline yet, a few months might depend on the definition of "few" :), but it's obviously not going to land within the next weeks, and we probably dont want to wait years. So we definitively dont want to wait too much to integrate in the portal to make sure it has time to reach end users. Might also have to prepare backports patches for distros in case that is needed?

I asked for documentation or a description of the XDG_CONFIG_HOME feature because that would make it more obvious what the desired order of lookup should be.

canonical point of entry is still the (rather long) bug https://bugzilla.mozilla.org/show_bug.cgi?id=259356. But to summarize quickly:

  • MOZ_LEGACY_HOME=1: we continue with the existing behavior
  • ~/.mozilla/ exists?: we continue with the existing behavior
  • otherwhise we $XDG_CONFIG_HOME

Choose a reason for hiding this comment

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

we probably dont want to wait years

Please don't. Flatpak browsers have been crippled for far too long.

Copy link

Choose a reason for hiding this comment

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

we probably dont want to wait years

Please don't. Flatpak browsers have been crippled for far too long.

before this gets out of control, there's 0 reason to hold the current PR for $XDG_CONFIG_HOME handling.

Copy link

Choose a reason for hiding this comment

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

To recap, the XDG_CONFIG_HOME handling effort in Firefox (https://bugzilla.mozilla.org/show_bug.cgi?id=259356) is about making sure that Firefox adheres to the platform conventions as specified in XDG's Basedir spec.

In the context of Firefox only, this is about the ability to read and write to an application-specific location, located at $XDG_CONFIG_HOME/mozilla/ instead of ~/.mozilla/. For compatibility reasons, the lookup order is ~/.mozilla/ first, then $XDG_CONFIG_HOME/mozilla/. Firefox may create files if they do not exist yet, and to force the current default of creating in ~/.mozilla/, MOZ_LEGACY_HOME=1 can be set (if unset, $XDG_CONFIG_HOME/mozilla/ would be created).

In the context of nativeMessaging and Firefox, it is relevant to know that this location is written to by external applications. Firefox treats them as read-only. Or, in the context of this portal, the portal reads from here and notifies Firefox of the discovered external native messaging applications.

The filesystem layout as seen by Firefox (inside the sandbox of the Snap package) can be independent of the filesystem layout outside of the sandbox (as seen by the portal). Therefore the mechanism to look up native messaging paths can be independent too.

Since the native messaging portal consumes the locations in a read-only fashion, there is no compatibility risk with unconditionally including $XDG_CONFIG_HOME/mozilla/native-messaging-hosts in the search_path (at the end). The only potential change in behavior is that the portal would be able to find and offer a native messaging host application, where Firefox would otherwise not be able to (until the patch stack associated with https://bugzilla.mozilla.org/show_bug.cgi?id=1905711 lands).

To close a session, the browser should:
1. close the process's stdin/stdout/stderr file descriptors
Copy link

Choose a reason for hiding this comment

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

Firefox doesn't close stdout/stderr, only stdin. It is possible for the native application to continue writing to stderr which could help with debugging if desired. Here is a suggestion that is more accurate.

Suggested change
1. close the process's stdin/stdout/stderr file descriptors
1. close the process's stdin file descriptors, optionally also stdout/stderr.

closed by the portal implementation, which will be signalled
via org.freedesktop.portal.Session::Closed.
To close a session, the browser should:
Copy link

Choose a reason for hiding this comment

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

The steps below assume the presence of a process + pipes. That is only if Start + GetPipes have already returned. If it is before, then we should just close the session, right? Here is a tweak to clarify when the following sequence should be followed:

Suggested change
To close a session, the browser should:
To close a session that progressed past Start() + GetPipes(), the browser should:

* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location
*/
/* Add per-user directories */
g_ptr_array_add (search_path, g_build_filename (g_get_home_dir (), ".mozilla", "native-messaging-hosts", NULL));
Copy link

Choose a reason for hiding this comment

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

Even if XDG_CONFIG_HOME were to be supported, then developers of native messaging components should still continue to write in the "old" location to support environments that do not support XDG_CONFIG_HOME. Therefore I think that it is acceptable to proceed with the current logic as is, and handle XDG_CONFIG_HOME later.

Do you agree with that assessment?

Copy link

@Rob--W Rob--W left a comment

Choose a reason for hiding this comment

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

Looks good from Mozilla / Firefox's perspective.

There is a request added by @lissyx about XDG_HOME_CONFIG at #705 (comment) ; let's add that as well so that the portal is forward compatible with Firefox's behavior in the "near" future.

One question before signing off, related to security: This portal allows Firefox extensions in Firefox to run a native messaging application outside of the sandbox, through the portal. The location of the binary can be specified in multiple locations (in this patch in get_manifest_search_path at web-extensions.c), which includes locations such as ~/.mozilla/native-messaging-hosts/. What ensures that these locations are inaccessible to Firefox? For example, if ~/.mozilla/ were to be readable AND writable to Firefox within the Snap package, then a vulnerability resulting in a file writing primitive within the sandbox can result in arbitrary code execution outside of the sandbox through the portal. Please document this clearly.

* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location
*/
/* Add per-user directories */
g_ptr_array_add (search_path, g_build_filename (g_get_home_dir (), ".mozilla", "native-messaging-hosts", NULL));
Copy link

Choose a reason for hiding this comment

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

To recap, the XDG_CONFIG_HOME handling effort in Firefox (https://bugzilla.mozilla.org/show_bug.cgi?id=259356) is about making sure that Firefox adheres to the platform conventions as specified in XDG's Basedir spec.

In the context of Firefox only, this is about the ability to read and write to an application-specific location, located at $XDG_CONFIG_HOME/mozilla/ instead of ~/.mozilla/. For compatibility reasons, the lookup order is ~/.mozilla/ first, then $XDG_CONFIG_HOME/mozilla/. Firefox may create files if they do not exist yet, and to force the current default of creating in ~/.mozilla/, MOZ_LEGACY_HOME=1 can be set (if unset, $XDG_CONFIG_HOME/mozilla/ would be created).

In the context of nativeMessaging and Firefox, it is relevant to know that this location is written to by external applications. Firefox treats them as read-only. Or, in the context of this portal, the portal reads from here and notifies Firefox of the discovered external native messaging applications.

The filesystem layout as seen by Firefox (inside the sandbox of the Snap package) can be independent of the filesystem layout outside of the sandbox (as seen by the portal). Therefore the mechanism to look up native messaging paths can be independent too.

Since the native messaging portal consumes the locations in a read-only fashion, there is no compatibility risk with unconditionally including $XDG_CONFIG_HOME/mozilla/native-messaging-hosts in the search_path (at the end). The only potential change in behavior is that the portal would be able to find and offer a native messaging host application, where Firefox would otherwise not be able to (until the patch stack associated with https://bugzilla.mozilla.org/show_bug.cgi?id=1905711 lands).

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
Status: Triaged
Status: No status
Development

Successfully merging this pull request may close these issues.

NativeMessaging portal for sandboxed browsers