freedomofpress / dangerzone

Take potentially dangerous PDFs, office documents, or images and convert them to safe PDFs
https://dangerzone.rocks/
GNU Affero General Public License v3.0
3.76k stars 171 forks source link

Inform user of outdated Docker Desktop Version #693

Open deeplow opened 10 months ago

deeplow commented 10 months ago

Even though we are shipping updates when critical vulnerabilities are found in the container image, the user may be running an outdated Docker Desktop version, which in the worst-case scenario is a container escape vulnerability, which completely undermines the security of the system.

We should add to the updater a way of notifying the user of Docker Desktop updates.

almet commented 5 months ago

As I understand it, the goal here is to have a way to prompt the user that their installed Docker Desktop version is outdated, when it's the case.

My understanding is that it should only be checked on Windows and macOS machines, as Linux doesn't rely on Docker Desktop.

How Docker desktop checks for the latest release.

As Docker Desktop is closed-source, I did some reverse engineering (using mitm-proxy) to find how Docker Desktop itself is looking for updates.

I found this URL is used on my Apple Silicon machine:

https://desktop.docker.com/mac/main/arm64/appcast.xml

It's returning XML unfortunately (I wish it would be JSON, but 🤷🏼‍♂️)

Here is the generic version:

https://desktop.docker.com/{platform}/main/{architecture}/appcast.xml

Where:

In fact, this appcast.xml file is defined by Sparkle and defined here.

Changes to the current code base

The current code base already has a way to do some checks, which is documented here, and I plan on doing the following changes:

  1. Change the name of the settings so they are more specific (changing updater_{check,latest_version,changelog} to updater_release_*), and providing a way to migrate the old settings to this new naming.

  2. Introducing new settings:

    • updater_docker_check which will make it possible for the user to enable / disable this check;
    • updater_docker_latest_version, storing the latest known remote version
    • eventually updater_docker_latest_changelog where we could cache the changelog (provided by appcast.xml)

    The settings would then look like this:

            # Updater settings
            "updater_last_check": None,  # last check in UNIX epoch (secs since 1970)
            "updater_errors": 0,
    
            # Update release settings
            "updater_release_check": None,
            "updater_release_latest_version": get_version(),
            "updater_release_latest_changelog": "",
    
            # Update docker settings
            "updater_docker_check": None,
            "updater_docker_latest_version": None,
            "updater_docker_latest_changelog": "",
  3. Use the already-in-place mechanism to do the checks, additionally checking for this new version check if it's enabled / not disabled, depending if we want to make this opt-in or opt-out (I would advise asking the user, so it's opt-in, to not leak anything to third parties without user consent).

Here are command lines for this. (Tested on macOS and windows) :

docker version --format '{{.Server.Platform.Name}}'
"Docker Desktop 4.19.0 (106363)"

Security considerations

Parsing a remotely loaded XML file can be an attack vector, as there are multiple XML vulnerabilities out there. To circumvent this, relying on defusedxml might help.

That would also be good to have another non-xml way to get the latest version, but that would diverge from the official way, as far as I'm aware.

apyrgio commented 5 months ago

Very cool Alexis, thanks a lot for the dig! Diving into your comments right now:

It seems that the only way to check if there's a new Docker Desktop version out is to:

  1. Hit a URL that's in a location that Docker dictates, and is not advertised by default.
  2. Parse the returned XML in a safe way (didn't know about defusedxml, nice), and grab the latest release.

    • Which is the latest release btw? Are we sure that the user can install it? I'm seeing some restrictions in the xml:

      <sparkle:minimumSystemVersion>
      ( [EditionID] != Home && [EditionID] != HomeEval && [EditionID] != Core && [EditionID] != CoreN && [EditionID] != CoreSingleLanguage && [EditionID] != CoreCountrySpecific && [BuildNumber] >= 19044 ) || ( [BuildNumber] >= 19044 )
      </sparkle:minimumSystemVersion>

      Also, I don't know if they have any beta/stable channels, which may confuse our parsing logic.

What I'm concerned about is that we need to handle the following cases:

  1. An appcast.xml URL that is not there (highly unlikely, but it can be the case)
  2. Parsing XML responses securely.
  3. Getting the wrong latest release in the appcast.xml (e.g., because the user is in a system that cannot upgrade to it)
  4. Disabled update checks by the user in Docker Desktop settings

From a UX perspective, based on your suggestions, I understand that we also need to:

  1. Prompt the user twice, once to enable Dangerzone updates, and once to check for Docker Desktop updates.
    • Do so only if Docker Desktop is installed, else the user may not understand what we ask from them.
  2. Grab the Docker Desktop changelog and show it to the user
  3. Have a backoff mechanism as well.

The security audit that spawned this issue says the following:

The assessment team recommends implementing a feature in Dangerzone that checks the host system for the latest version of Docker/Docker Desktop upon startup. If the feature detects an outdated Docker installation, Dangerzone could then provide a warning to the user, recommending an update. This precaution aims to mitigate the risk of attackers exploiting vulnerabilities within the container to escape to the host system.

It assumed that a mechanism existed locally where you could check for the latest version of Docker. It would be great if docker info or docker version provided this info, but unfortunately it doesn't. With that said, I believe that reimplementing a subset of the Sparkle client in our application for another app (Docker) would open a different can of worms than the ones we have now. It can work, sure, but we have to be very careful with regards to security and user privacy.

So, ultimately I think we should think this through before going forward. Maybe there's another way with less corner cases that can work here, at least for a subset of the affected users.

almet commented 5 months ago

Thanks for the feedback. I agree that redoing a sparkle client seems superfluous, and I share your concerns about the restrictions present in the XML, which seem to follow a language we don't know.

You comment made me look for alternate ways to know the latest available version of Docker Desktop. Maybe Docker Desktop is storing the data somewhere we can reuse ? 🤔 That would mean the checks are done out of band for us, and that we can reuse their code by just looking at the results.

But… I'm not even sure that's possible. I'll have a closer look.

almet commented 5 months ago

After some tinkering on my system, I'm unable to find this information. I'm putting here my findings, but I'm not sure they're relevant.

I've been looking at the following locations:

~/Library/Group\ Containers/group.com.docker/, contains some version information, but it's fairly weird, I'm not sure we should rely on it:

cat unleash-repo-schema-v1-Docker\ Desktop.json | jq ".SidebarLayout"
{
  "name": "SidebarLayout",
  "description": "Enables new Sidebar Layout for Docker Desktop",
  "enabled": true,
  "strategies": [
    {
      "id": 0,
      "name": "flexibleRollout",
      "constraints": [
        {
          "contextName": "version",
          "operator": "SEMVER_LT",
          "values": [],
          "value": "4.31.0",
          "caseInsensitive": false,
          "inverted": true
        }
      ],
      "parameters": {
        "groupId": "SidebarLayout",
        "rollout": "100",
        "stickiness": "default"
      },
      "segments": null
    }
  ],
  "createdAt": "0001-01-01T00:00:00Z",
  "strategy": "",
  "parameters": null,
  "variants": []
}

There is a settings.json file there, where we can see if the automatic updates are activated, what's the channel, etc. It might prove useful.

cat settings.json | jq | grep -i Update

  "updateAvailableTime": 1692453100698,
  "updateInstallTime": 0,
  "disableUpdate": false,
  "acceptCanaryUpdates": false,
  "useNightlyBuildUpdates": false,
  "autoDownloadUpdates": false,
  "updateHostsFile": false,
almet commented 5 months ago

FYI, I'm not pursuing the effort on this pull request because the path forward is not clear. I've pushed the changes (related to renaming the settings) to the 2024-06-docker-desktop-version-check branch in case it's useful for the future.

apyrgio commented 5 months ago

Thanks for documenting your findings Alexis. They will be really useful once we decide to work again on this issue. For the record, I was thinking that we could at least notify the user that their Docker Desktop installation is outdated if, e.g., it's been 6 months since the last release (docker version returns the build date of the software, for instance).

This is one possible metric we can use that does not rely on external sources. It doesn't cover every use case, sure, but it will help the vast majority of our users, who have installed Docker Desktop at some point and then forgot about it.

One other thing I was considering is if Docker Desktop already has such a mechanism. My thinking was that:

So, if Docker Desktop informs users about important upgrades on startup, we're good.

almet commented 2 months ago

One other thing I was considering is if Docker Desktop already has such a mechanism. My thinking was that:

  • Our users need to start Docker Desktop manually, in order to run Dangerzone
  • Sparkle project can notify users about updates, if they are critical

So, if Docker Desktop informs users about important upgrades on startup, we're good.

This is the case: Docker Desktop is displaying a warning to the users when they start the program, at least on OSX (I'm not sure on Windows, but I expect this to be the same, we should check).

Here is how this is displayed to the end users:

  1. The small tray icon has a exclamation mark next to it (I'm not sure it's related, but I think)
image
  1. When opening the application, on the bottom-right corner there is a "New version available" message coming up.
image

None of these are blocking, though.

Also worth nothing: in the "software updates" settings, there is an "Always download updates" checkbox:

image

I was thinking that we could at least notify the user that their Docker Desktop installation is outdated if, e.g., it's been 6 months since the last release (docker version returns the build date of the software, for instance).

That would certainly be useful. I'm also thinking we could add a minimal supported version in the dangerzone code somewhere (potentially even one for Windows and one for MacOS), and warn the user if the installed version doesn't match this.

Because we're issuing a release every two months (approximately), it would allow to warn our users to upgrade quicker than the 6 month cap you're mentioning.

We could check the version returned by docker version:

$ docker version --format '{{.Server.Platform.Name}}'
"Docker Desktop 4.19.0 (106363)"
apyrgio commented 2 months ago

This is the case: Docker Desktop is displaying a warning to the users when they start the program, at least on OSX

Pretty awesome dig, and thanks a lot for the image. So, it seems that Docker does not urge the users to update, but it does inform them about it (kind of like our own update notifications).

That would certainly be useful. I'm also thinking we could add a minimal supported version in the dangerzone code somewhere (potentially even one for Windows and one for MacOS), and warn the user if the installed version doesn't match this.

That's actually a really good idea! Are you thinking of showing a warning notification to our users, or something more visible, like a pop-up? Note that for Dangerzone updates, we show just a notification bubble for the time being.

almet commented 2 months ago

That's actually a really good idea! Are you thinking of showing a warning notification to our users, or something more visible, like a pop-up? Note that for Dangerzone updates, we show just a notification bubble for the time being.

That's a good question, and the answer depends on how we want this to be annoying for our users. If we don't want to be too annoying, reusing the notification bubble might be enough.