Skip to content

ProductUpgrader: platform specific upgrade directories#1224

Merged
jamill merged 2 commits into
microsoft:masterfrom
jamill:upgrader_tasks/directory_structure
Jun 18, 2019
Merged

ProductUpgrader: platform specific upgrade directories#1224
jamill merged 2 commits into
microsoft:masterfrom
jamill:upgrader_tasks/directory_structure

Conversation

@jamill
Copy link
Copy Markdown
Member

@jamill jamill commented May 30, 2019

Different platforms have different file system layouts / capabilities. The existing upgrade logic is Windows centric and does not match the behaviors of other systems.

Upgrade components that interact with the fileystem:


Component description data scope Windows Mac
Upgrader Application Temporary copy of upgrader application upgrade main application Protected ProgramData\GVFS\GVFS.Upgrade\Tools /usr/local/vfsforgit_upgrader/Tools
gvfs.config config file for upgrade related settings Protected ProgramData\GVFS\gvfs.config /usr/local/vfsforgit/gvfs.config
Upgrade Package Download Location to download upgrade package Protected ProgramData\GVFS\GVFS.Upgrade\Download /usr/local/vfsforgit_upgrader/Download
Upgrader Package Unpack Location to unpack the downloaded update package Protected ProgramData\GVFS\GVFS.Upgrade\Download\InstallerTemp /usr/local/vfsforgit_upgrader/Download/InstallerTemp
Upgrader Logs Directory to write upgrade logs to Writable by user account ProgramData\GVFS\GVFS.Upgrade\UpgraderLogs ~/Library/Application Support/GVFS/GVFS.Upgrade/UpgraderLogs
Upgrade Available File File indicating whether an upgrade is available Written by GVFS.Service process

Windows: protected

Mac: writable by user account
ProgramData\GVFS\GVFS.Upgrade\Tools\HighestAvailableVersion ~/Library/Application Support/GVFS/GVFS.Upgrade/HighestAvailableVersion

Data Scopes


This is not generally applicable across platforms, but is a model for how things are set up on Mac.

Scope Usage Windows Mac
Non-protected Data Unprivileged data that user accounts can access. Logs directory Logs and HighestAvailableVersion file.

~/Library/Application Support/GVFS/GVFS.Upgrade
Protected Data Requires root / elevation to mutate ProgramData\GVFS\GVFS.Upgrade /usr/local/vfsforgit_upgrader

Motivation

On Windows, the upgrade related directories are located under the "service" level directory structure, and is system scoped and requires elevated permissions to modify (except for specific exemptions). The base directory is ProgramData\GVFS\GVFS.Upgrade. We have to tweak the permissions on the UpgraderLogs directory in order to allow non-elevated access, as non-elevated processes need to be able to write logs here.

On Mac, the root of the "service" data directories is a user scoped, and not system scoped. As the upgrade flow uses ACLs to protect the data, we need to find another directory.

This is a proposed change to enable VFS for Git to account for the platform differences.

Mac File System Hierarchy

There are several candidates for where we can place "protected" data, including /usr/local, or /var. The proposed location for the Upgrader app and related data is: /usr/local/vfsforgit_upgrader, but I could see putting the data (and / or) application in /var as well.

@derrickstolee
Copy link
Copy Markdown
Contributor

@jamill Thanks for the detailed PR description! It is truly enlightening all the things that are going on here and how we need to change things for Mac.

Comment thread GVFS/GVFS.Common/MacProductUpgraderInfo.cs Outdated
Comment thread GVFS/GVFS.Common/ProductUpgraderInfo.cs Outdated
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch from 37c5dea to 5c744fa Compare May 31, 2019 15:59
Comment thread GVFS/GVFS.Common/ProductUpgraderInfoImpl.cs Outdated
@jamill jamill mentioned this pull request May 31, 2019
15 tasks
@nickgra
Copy link
Copy Markdown
Member

nickgra commented May 31, 2019

I reviewed the description/design and it looks good to me!

Your model works for protecting the vfsforgit_upgrader folder if the user hasn't messed with the permissions on the /usr/local folder, but if the mode was modified to allow anyone to write to the folder, we could be vulnerable to the attack where someone could replace the vfsforgit_upgrader folder with their own content and have VFS4G execute it as admin. I don't know enough about ACLs on Windows, but we might be vulnerable in the same way there.
(I don't have any immediate ideas on how we could fix this off the top of my head, besides resetting the permissions on /usr/local before we lay down the upgrader folder. I'll keep thinking on it though.)

Also, there are some places in your PR description where you have /usr/local.vfsforgit_upgrader instead of /usr/local/vfsforgit_upgrader.

Copy link
Copy Markdown
Contributor

@alameenshah alameenshah left a comment

Choose a reason for hiding this comment

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

The approach looks good. Couple of things about Protected Data that can be potentially optimized on the Mac.

Is Protected Data directory really needed on a Mac? Alternate options to consider -

  1. The pattern of copying upgrader tool to a temporary directory on Windows exists on the premise that assemblies that are open get locked and an upgrade installer cannot overwrite them (as long as they remain open). By copying upgrader tool to a temp directory and shutting down running instances of gvfs, we ensure that none of the installed gvfs assemblies remain open during upgrade. However, on the Mac, this does not apply - it is possible to overwrite/delete an binary that is open & running. So /usr/local/vfsforgit/gvfs upgrade can run and upgrade itself.

  2. Upgrade Download & Unpack (the upgrade payload). Can we utilize the fact that our installers are code signed using Microsoft certificate, to avoid the need for additional protection? On the newest Macs, Apple installer will not let user install an unsigned installer package, unless -allowUntrusted command line option is specified. This should enable us to download the installer payload in an unprotected temp directory.

@alameenshah
Copy link
Copy Markdown
Contributor

One more thought about using /usr/local.vfsforgit_upgrader/Download/InstallerTemp to store temp files. There is this directory /private/var/tmp which is special directory with a sticky bit set. We wouldn't need to set custom ACLs for protection.

@jamill
Copy link
Copy Markdown
Member Author

jamill commented Jun 3, 2019

Thanks for the feedback!

@alameenshah -

Is Protected Data directory really needed on a Mac?

Good question. As you pointed out, locked files are not an issue on Mac, but I was not sure if there could be other problems. For instance, could an operation cause an assembly load after we have overwritten the original application directory, and result in an "unexpected" assembly being found?

Can we utilize the fact that our installers are code signed using Microsoft certificate, to avoid the need for additional protection?

Another good point - this will help with the installer(s) themselves, but there could be scripts or other artifacts included with the installer package.

@nickgra -

but if the mode was modified to allow anyone to write to the folder, we could be vulnerable to the attack

Good point. We had an assumption that the user would not tweak this part of their system. We could include an additional check that the system directories are ACL'ed as we expect, and handle the cases where it isn't (maybe refuse to install?)

@alameenshah alameenshah requested a review from pmj June 3, 2019 16:14
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch 2 times, most recently from c4549b0 to 4bfd666 Compare June 3, 2019 21:26
@jamill jamill changed the title WIP: ProductUpgrader: platform specific upgrade directories ProductUpgrader: platform specific upgrade directories Jun 4, 2019
@jamill jamill removed the WIP label Jun 4, 2019
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch 2 times, most recently from d7f39bd to cfed1bd Compare June 11, 2019 02:32
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch 2 times, most recently from bc76497 to b412425 Compare June 13, 2019 17:03
Comment thread GVFS/GVFS.Common/GitHubUpgrader.cs Outdated
Copy link
Copy Markdown
Member

@kewillford kewillford left a comment

Choose a reason for hiding this comment

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

I think we need to come up with the right terminology for these and make sure it is consistent throughout the code base or things get confusing.

Comment thread GVFS/GVFS.Common/ProductUpgraderInfo.cs
Comment thread GVFS/GVFS.Platform.Mac/MacProductUpgraderPlatformStrategy.cs Outdated
Comment thread GVFS/GVFS/CommandLine/DiagnoseVerb.cs
{
this.Config = upgraderConfig;

string upgradesDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: follow up on why we were creating this directory, and see if we still need to create a directory here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if something else would have created the directory by the time RecordHighestAvailableVersion runs, but it looks like if the directory did not exist for RecordHighestAvailableVersion the call to WriteAllText in RecordHighestAvailableVersion would throw a DirectoryNotFoundException.

There might be other methods that GetUpgradesDirectoryPath that similarly assume the directory already exists.

@jamill jamill force-pushed the upgrader_tasks/directory_structure branch 2 times, most recently from c70aaf7 to a194f05 Compare June 14, 2019 17:38
Copy link
Copy Markdown
Member

@wilbaker wilbaker left a comment

Choose a reason for hiding this comment

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

Overall I think the changes look good, just left some minor questions/comments

Comment thread GVFS/GVFS.Common/GVFSPlatform.cs Outdated
{
this.Config = upgraderConfig;

string upgradesDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if something else would have created the directory by the time RecordHighestAvailableVersion runs, but it looks like if the directory did not exist for RecordHighestAvailableVersion the call to WriteAllText in RecordHighestAvailableVersion would throw a DirectoryNotFoundException.

There might be other methods that GetUpgradesDirectoryPath that similarly assume the directory already exists.

Comment thread GVFS/GVFS.Common/ProductUpgraderInfo.Shared.cs
ProductUpgraderInfo.DownloadDirectory);
}

public static string GetHighestAvailableVersionDirectory()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can/should this method be removed and replaced with calls to GVFSPlatform.Instance.GetUpgradeHighestAvailableVersionDirectory() directly?

ProductUpgraderInfo.ApplicationDirectory);
}

public static string GetParentLogDirectoryPath()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can/should this method be removed and replaced with calls to GVFSPlatform.Instance.GetUpgradeLogDirectoryParentDirectory() directly?

{
get
{
return Path.Combine(this.Constants.GVFSBinDirectoryPath, LocalGVFSConfig.FileName);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note: I am not an expert on how file permissions work on Mac, so there might be no issues here!

Does sudo care at all about what group a user is in? I'm wondering if two users are modifying /usr/local/vfsforgit/gvfs.config is there any risk that one of them will block the other from being able to make further changes (using sudo)?

Similarly, is there any chance that one user's changes might prevent another users from being able to read the file (without using sudo)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Great question(s)!

Root is its own account, so when running a command via sudo, root will be the owner of this file. The mode bits on the file will be such that root is the only account that can modify the file, while it will be readable by everyone. If two different users attempt to modify this file via a sudo command, it will be the root account that is making changes.

/cc @jeffhostetler to check my understanding :)

@jamill jamill force-pushed the upgrader_tasks/directory_structure branch 5 times, most recently from 7950398 to c299152 Compare June 14, 2019 20:41
Copy link
Copy Markdown
Member

@nickgra nickgra left a comment

Choose a reason for hiding this comment

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

This is looking good to me, I agree with Kevin and William on the naming suggestions to ensure that we're using the same terminology throughout the codebase.

Comment thread GVFS/GVFS.Common/GVFSPlatform.cs Outdated
Copy link
Copy Markdown
Member

@kewillford kewillford 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 to me. Just a couple of naming things.

Comment thread GVFS/GVFS.Common/ProductUpgrader.cs Outdated
Comment thread GVFS/GVFS.Common/ProductUpgrader.cs Outdated
The config file location currently exists in the same directory as the GVFS
service data directory. On Mac, the GVFS Service directory is per user, and not
per machine. Even if the service is per user on Mac, we still would like the
config to be per machine.

This change enables the GVFS config file to exist at a location independent of
the GVFS Service data directory. On Windows, the location will not change, but
on macOS, it will now exist in the GVFS binary location.
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch from c299152 to 96a51c1 Compare June 17, 2019 19:52
Different platform have different structures for where the upgrade
application and logs need to go. This change introduces the
architecture for making this platform specific.

Description of different components involved in upgrade and how they interact
with different platforms.
---

Component                | description
-----------------------  | -----------
Upgrader Application     | Temporary copy of upgrader application upgrade main application
gvfs.config              | config file for upgrade related settings
Upgrade Package Download | Location to download upgrade package
Upgrader Package Unpack  | Location to unpack the downloaded update package
Upgrader Logs            | Directory to write upgrade logs to
Upgrade Available File   | File indicating whether an upgrade is available

Component                | data scope                      | Windows                                                     | Mac
------------------------ | ------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------
Upgrader Application     | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Tools                         | /usr/local/vfsforgit_upgrader/Tools
gvfs.config              | Protected                       | ProgramData\GVFS\gvfs.config                                | /usr/local/vfsforgit/gvfs.config
Upgrade Package Download | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Download                      | /usr/local/vfsforgit_upgrader/Download
Upgrader Package Unpack  | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Download\InstallerTemp        | /usr/local/vfsforgit_upgrader/Download/InstallerTemp
Upgrader Logs            | Writable by user account        | ProgramData\GVFS\GVFS.Upgrade\UpgraderLogs                  | ~/Library/Application Support/GVFS/GVFS.Upgrade/UpgraderLogs
Upgrade Available File   | Written by GVFS.Service process | ProgramData\GVFS\GVFS.Upgrade\Tools\HighestAvailableVersion | ~/Library/Application Support/GVFS/GVFS.Upgrade/HighestAvailableVersion
@jamill jamill force-pushed the upgrader_tasks/directory_structure branch from 96a51c1 to 0e5fb4a Compare June 17, 2019 20:37
@jamill jamill merged commit 8a9038a into microsoft:master Jun 18, 2019
@jrbriggs jrbriggs added this to the M155 milestone Jun 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants