Xdg-desktop-portal file loss

There’s been a bug report about data loss when using the XDG portals on one of the snaps I package. Because of the nature of the bug, it seems it likely effects far more people than myself, and I thought I’d come here to see if anyone could offer any guidance on how to proceed.

To cover the basics, this is running on an Ubuntu 20.04.3 machine, the snap involved is pinta (though I imagine it wouldn’t matter). Everything is updated. There’s no other PPA’s or third party repo’s involved on this system.

In principal, when saving a file; if the file has no file extension the data is simply lost. That’s not quite specifically what happens, but this is the user experience.

Imagine drawing a picture, and saving the file with a name like test. The save completes, and the GUI will actually show the file saved as test.jpg. Running the command flatpak permissions will even show that the documents portal has been used in this case, and there should be a link between /run/user/$UID/docs and wherever the user actually saved.

But it isn’t the case, the data exists in /run, but isn’t copied across to where the user saves. The data will be lost when the user restarts the application or reboots (as /run is cleared on reboot!).

Simply adding a file extension is enough to prevent this, similarly, removing the use of the XDG portals also fixes this.

So why am I asking here? Well, I’m unsure where to proceed. It’s a bug that probably effects significantly more users than just myself, because it’s part of the portal code that’s outside the snaps domain. I can’t verify if it’s a problem with upstream because I wouldn’t trust my ability to build it. I can’t run other distributions on this system because of limitations with my virtual machine software, and so I’m essentially left with report this on the Portals github and report it here; I will report it on the Github shortly if there’s no immediate responses, but I’m familiar with the people here and given this may be an Ubuntu specific problem to boot, there might be more knowledge here.

Can anyone chime in on where to proceed from here?

@jamesh any thoughts on this?

Just as an update, I’ve manually patched the $SNAP/meta/snap.yaml file in the latest revision (19) of Pinta to avoid this bug for now; using the unsquashfs && snap pack route to unset GTK_USE_PORTAL, so it’s bit for bit identical everywhere else but if you do end up using this package to test this now be sure to compensate for that.

It’s actually specifically turned off rather than just undefined just to prevent users potentially turning it back on, so you’d need to either manually revert it or set the export in a snap run --shell environment first.

Edit:

I also did a test on another of my snaps using the portals, this time, I tried one with core20/kde-neon extensions rather than core20/gnome-3-38; the impact was the same, the lack of file extension was causing the file to only appear in /run, and adding an extension results in it saving in the intended location just fine.

Reasonably then, I’d assume that this isn’t anything unique to specifically pinta, (both at the application + packaging level) which is unfortunate because it means that the overall impact is unfortunately looking quite severe.

Edit 2:

Just for the sake of clarity, I’ve uploaded a video to my Google Drive that shows this in action

From the perspective of the portals, the application is misbehaving: the application has no permission to write a file named test.jpg if the user has only granted access to a file called test.

There is one remaining question though: why didn’t the app receive any error when it tried to write to the wrong file? The answer to that is a little more complicated.

When the user picks a file name in the portal “save as” dialog, the application is provided with a path that looks something like $XDG_RUNTIME_DIR/doc/${doc_id}/${file_basename}. The ${doc_id} is a unique ID assigned to the permission to access the file, and the $XDG_RUNTIME_DIR/doc/${doc_id} directory will either contain just the chosen file, or be empty if that file doesn’t exist yet.

The document portal allows the app to create new files in the directory with other names, but that isn’t intended as a way for the app to write additional files. Instead, it is intended to handle the common idiom of writing a new version of the document to a temporary file in the same directory, and then renaming it over the final file name if the previous step was successful.

There is no fixed standard for how applications might pick these temporary file names, so the document portal just considers anything not matching the chosen file name to be a temporary file.

That leads to one final question: where on the file system does the test.jpg file Pinta created live on the file system exactly? Looking at the code, you should see a .xdp-test.jpg-${random} file in the directory containing the chosen file:

This temporary file will be deleted when the document portal exits, which would usually happen when the user logs out.

Thanks for the response fellow James

What you’ve said makes sense, and I can follow your explaination, but even though it’s a solid technical explaination, this is still a severe problem from the user experience which is ultimately the most important aspect. The Gnome/KDE extensions automatically set GTK_USE_PORTAL; whether or not the application developers even know about it. Some platforms like Electron have started defaulting to the portals with no on/off mechanism. Their individual implementations might be wrong, but the problem is a stain on the ecosystem in general.

So while I will happily report this back up to Pinta as a problem that needs fixing at an application level, and with your explaination to assist, it should be easily fixed. We can’t fix every application, some will be closed source, some will be unwilling to take patches for Snap/Flatpak specific issues, some maintainers just won’t be reachable anymore.

It’d make me question just how valid it is that GTK_USE_PORTAL=1 is used in the extensions implicitly, because what we’re effectively trading is access to the entire filesystem, for the quite probable risk of data loss. We’re not being proactive there in telling the snap maintainer that we’re enabling a switch behind their back that might just so happen to cause data loss, and it’s up to them to figure out whether it does or not, when the default version works just fine. The sandboxing being annoying is better than the sandboxing gobbling data.

I know that I’m not providing solutions to the core issue here, and maybe the core issue is unresolvable. But IMO I really doubt that this one user who’s told me about this problem is the only user who’s ever experienced it. Across all users of Snaps, all applications, over all time, we’ll never know just how much has been lost because of this error, and regardless of which component specifically is to blame, it looks bad for everyone involved.

So thanks again for the explaination because it’ll definitely help Pinta & some others directly, but to me personally, this is a massive issue regardless even if there is a good excuse for it, and in an ideal world, there’d be a way to get knowledge out there about this better.

Edit: I’m also struggling how to justify this in a cross platform sense. Pinta adding the file extension automatically is literally essential on E.G Windows, where the extension is critical for the OS to detect the purpose of the file. It’s hard to justify this as incorrect behaviour when the majority of people will be accustomed to it being the default behaviour.

For something like that to fit within the security framework, the decision to append the extension would have to happen within the file chooser, before deciding what path to share with the app through the document portal. But even that has potential security concerns, if it could trick the user into granting access to something they didn’t intend to.

The simplest solution would probably be to patch out the filename modification, or convince upstream to add a compile option to disable it.

1 Like

A huge issue is that opening a file chooser in Qt with the file chooser implementation of xdg-desktop-portal-gtk drops the default suffix. The user is presented with an empty textbox and types something in. Most applications append the suffix themselves and the file is “lost” as described above by @James-Carroll.

But even if the application does not append a suffix, the file is seemingly “lost” because the user won’t find it again in the file chooser when trying to open it. Usually, there is a suffix filter set as an allowed extension, which prevents the dialog from displaying the previously saved suffix-less file.

Re: the file loss problem

This issue is especially insidious because older portal versions behave differently compared to recent ones. In 21.10, the portal returns the actual path without the proxying over the document portal if the snap has access to it anyway. So the developer tests on their 21.10 machine, sees that it works, and is happy. However, the user might be on 20.04, where every access uses the document portal. In this case, everything comes crashing down.

2 Likes

Regarding the portal versions being different, it brings up a point of how the portals treat files vs folders. There’s no folder support in 20.04, so any folder access won’t have the proxying. Newer Portal versions do have folder support however, so I’m focusing on these.

I haven’t managed to test if the UI is any different but my expectation is that it runs exactly the same apart from it obviously selects folders, not files.

I’d have to question then, what’s the difference? The user can give access to an entire folder if they want to, the snap could be malicious and trick them into making a poor choice (“give me access to $HOME!”). I’m unsure if this is recursive (e.g, would that then enable stealing $HOME/.ssh ?) because I haven’t tested it, but it doesn’t matter in this instance.

If it’s already considered that individual folders are fair game, then IMO, the better hypothetical API is to select a location, which may be either folder or file. If it’s folder, then it obviously mounts the folder. If it’s a file, it still mounts the entire folder, but gives the path of the file.

From a security POV the individual file access is obviously superior, but speaking practically, we’ve already conceded that folders are a valid target, except now we have a dichotomy where you think you have access to a folder with one API, but actually don’t.

1 Like