commit 3b5281a4191efd5336328932fbe74e17e9eb7614 Author: Yawning Angel yawning@schwanenlied.me Date: Fri Dec 23 03:02:16 2016 +0000
Bug 20851: If the incremental update fails, fall back to the complete update. --- ChangeLog | 2 + .../internal/ui/config/config.go | 12 ++ .../sandboxed-tor-browser/internal/ui/launch.go | 6 +- .../sandboxed-tor-browser/internal/ui/update.go | 226 +++++++++++++++------ 4 files changed, 175 insertions(+), 71 deletions(-)
diff --git a/ChangeLog b/ChangeLog index c7d7e4a..5344f14 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,6 @@ Changes in version 0.0.3 - UNRELEASED: + * Bug 20851: If the incremental update fails, fall back to the complete + update. * Bug 21055: Fall back gracefully if the Adwaita theme is not present. * Bug 20791: Fetch install/update metadata using onions. * Bug 20979: runtime/cgo: pthread_create failed: Resource temporarily diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go index a1af1cd..9ffbd5c 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go @@ -302,6 +302,9 @@ type Config struct { // ForceUpdate is set if the installed bundle is known to be obsolete. ForceUpdate bool `json:"forceUpdate"`
+ // SkipPartialUpdate is set if the partial update has failed to apply. + SkipPartialUpdate bool `json:"skipPartialUpdate"` + // Tor is the Tor network configuration. Tor Tor `json:"tor,omitEmpty"`
@@ -381,6 +384,15 @@ func (cfg *Config) SetForceUpdate(b bool) { } }
+// SetSkipPartailUpdate sets the bundle as needing a complete update as opposed +// to a partial one, and marks the config dirty. +func (cfg *Config) SetSkipPartialUpdate(b bool) { + if cfg.SkipPartialUpdate != b { + cfg.SkipPartialUpdate = b + cfg.isDirty = true + } +} + // Sanitize validates the config, and brings it inline with reality. func (cfg *Config) Sanitize() { if !utils.DirExists(cfg.Sandbox.DownloadsDir) { diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go index 6a86dba..49b7663 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/launch.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/launch.go @@ -63,15 +63,13 @@ func (c *Common) DoLaunch(async *Async, checkUpdates bool) { // Start tor if required. log.Printf("launch: Connecting to the Tor network.") async.UpdateProgress("Connecting to the Tor network.") - dialFn, err := c.launchTor(async, false) - if err != nil { - async.Err = err + if _, async.Err = c.launchTor(async, false); async.Err != nil { return }
// If an update check is needed, check for updates. if checkUpdates { - c.doUpdate(async, dialFn) + c.doUpdate(async) if async.Err != nil { return } diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/update.go b/src/cmd/sandboxed-tor-browser/internal/ui/update.go index 7c5f812..1994e64 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/update.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/update.go @@ -25,16 +25,29 @@ import (
"cmd/sandboxed-tor-browser/internal/installer" "cmd/sandboxed-tor-browser/internal/sandbox" + "cmd/sandboxed-tor-browser/internal/tor" . "cmd/sandboxed-tor-browser/internal/ui/async" )
-func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEntry { +// CheckUpdate queries the update server to see if an update for the current +// bundle is available. +func (c *Common) CheckUpdate(async *Async) *installer.UpdateEntry { // Check for updates. log.Printf("update: Checking for updates.") async.UpdateProgress("Checking for updates.")
// Create the async HTTP client. - client := newHPKPGrabClient(dialFn) + if c.tor == nil { + async.Err = tor.ErrTorNotRunning + return nil + } + dialer, err := c.tor.Dialer() + if err != nil { + async.Err = err + return nil + } + + client := newHPKPGrabClient(dialer.Dial)
// Determine where the update metadata should be fetched from. updateURLs := []string{} @@ -57,7 +70,9 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEnt for _, url := range updateURLs { log.Printf("update: Metadata URL: %v", url) async.Err = nil // Clear errors per fetch. - if b := async.Grab(client, url, nil); async.Err != nil { + if b := async.Grab(client, url, nil); async.Err == ErrCanceled { + return nil + } else if async.Err != nil { log.Printf("update: Metadata download failed: %v", async.Err) continue } else if update, async.Err = installer.GetUpdateEntry(b); async.Err != nil { @@ -69,13 +84,14 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEnt }
if !fetchOk { - // This should be set from the last update attempt... - if async.Err == nil { - async.Err = fmt.Errorf("failed to download update metadata") - } + // The last update attempt likely isn't the only relevant error, + // just set this to something that won't terrify users, more detailed + // diagnostics are avaialble in the log. + async.Err = fmt.Errorf("failed to download update metadata") return nil }
+ // If there is an update, tag the installed bundle as stale... if update == nil { log.Printf("update: Installed bundle is current.") c.Cfg.SetForceUpdate(false) @@ -84,6 +100,7 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEnt c.Cfg.SetForceUpdate(true) }
+ // ... and flush the config. if async.Err = c.Cfg.Sync(); async.Err != nil { return nil } @@ -91,38 +108,26 @@ func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEnt return update }
-func (c *Common) doUpdate(async *Async, dialFn dialFunc) { - // This attempts to follow the process that Firefox uses to check for - // updates. https://wiki.mozilla.org/Software_Update:Checking_For_Updates +// FetchUpdate downloads the update specified by the patch over tor, and +// validates it with the hash in the patch datastructure, and the known MAR +// signing keys. +func (c *Common) FetchUpdate(async *Async, patch *installer.Patch) []byte { + var dialFn dialFunc
- // Check for updates. - update := c.CheckUpdate(async, dialFn) - if async.Err != nil || update == nil { - return - } - - // Ensure that the update entry version is actually neweer. - if !c.Manif.BundleUpdateVersionValid(update.AppVersion) { - log.Printf("update: Update server provided a downgrade: '%v'", update.AppVersion) - async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion) - return - } - - // Figure out the best MAR to download. - patches := make(map[string]*installer.Patch) - for _, v := range update.Patch { - if patches[v.Type] != nil { - async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type) - return + // Launch the tor daemon if needed. + if c.tor == nil { + dialFn, async.Err = c.launchTor(async, false) + if async.Err != nil { + return nil } - patches[v.Type] = &v - } - patch := patches["partial"] // Favor the delta update mechanism. - if patch == nil { - if patch = patches["complete"]; patch == nil { - async.Err = fmt.Errorf("no suitable MAR file found") - return + } else { + // Otherwise, retreive the dialer. + dialer, err := c.tor.Dialer() + if err != nil { + async.Err = err + return nil } + dialFn = dialer.Dial }
// Download the MAR file. @@ -132,7 +137,7 @@ func (c *Common) doUpdate(async *Async, dialFn dialFunc) { var mar []byte client := newHPKPGrabClient(dialFn) if mar = async.Grab(client, patch.Url, func(s string) { async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); async.Err != nil { - return + return nil }
log.Printf("update: Validating Tor Browser Update.") @@ -142,64 +147,151 @@ func (c *Common) doUpdate(async *Async, dialFn dialFunc) { expectedHash, err := hex.DecodeString(patch.HashValue) if err != nil { async.Err = fmt.Errorf("failed to decode HashValue: %v", err) - return + return nil } switch patch.HashFunction { case "SHA512": derivedHash := sha512.Sum512(mar) if !bytes.Equal(expectedHash, derivedHash[:]) { async.Err = fmt.Errorf("downloaded hash does not match patch metadata") - return + return nil } default: async.Err = fmt.Errorf("unsupported hash function: '%v'", patch.HashFunction) - return + return nil }
// ... and verify the signature block in the MAR with our copy of the key. if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil { - return + return nil }
- // Shutdown the old tor now. - if c.tor != nil { - log.Printf("update: Shutting down old tor.") - c.tor.Shutdown() - c.tor = nil - } + return mar +}
- // Apply the update. - log.Printf("update: Updating Tor Browser.") - async.UpdateProgress("Updating Tor Browser.") +func (c *Common) doUpdate(async *Async) { + // This attempts to follow the process that Firefox uses to check for + // updates. https://wiki.mozilla.org/Software_Update:Checking_For_Updates
- async.ToUI <- false // Lock out canceling. + const ( + patchPartial = "partial" + patchComplete = "complete" + )
- if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil { + // Check for updates. + update := c.CheckUpdate(async) + if async.Err != nil || update == nil { + // Something either broke, or the bundle is up to date. The caller + // needs to check async.Err, and either way there's nothing more that + // can be done. return }
- // Reinstall the autoconfig stuff. - if async.Err = writeAutoconfig(c.Cfg); async.Err != nil { + // Ensure that the update entry version is actually neweer. + if !c.Manif.BundleUpdateVersionValid(update.AppVersion) { + log.Printf("update: Update server provided a downgrade: '%v'", update.AppVersion) + async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion) return }
- // Update the maniftest and config. - c.Manif.SetVersion(update.AppVersion) - if async.Err = c.Manif.Sync(); async.Err != nil { - return + // Figure out the best MAR to download. + patches := make(map[string]*installer.Patch) + for i := 0; i < len(update.Patch); i++ { + v := &update.Patch[i] + if patches[v.Type] != nil { + async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type) + return + } + patches[v.Type] = v } - c.Cfg.SetForceUpdate(false) - if async.Err = c.Cfg.Sync(); async.Err != nil { - return + + patchTypes := []string{} + if !c.Cfg.SkipPartialUpdate { + patchTypes = append(patchTypes, patchPartial) } + patchTypes = append(patchTypes, patchComplete) + + // Cycle through the patch types, and apply the "best" one. + nrAttempts := 0 + for _, patchType := range patchTypes { + async.Err = nil + + patch := patches[patchType] + if patch == nil { + continue + } + + nrAttempts++ + mar := c.FetchUpdate(async, patch) + if async.Err == ErrCanceled { + return + } else if async.Err != nil { + log.Printf("update: Failed to fetch update: %v", async.Err) + continue + } + if mar == nil { + panic("update: no MAR returned from successful fetch") + } + + // Shutdown the old tor now. + if c.tor != nil { + log.Printf("update: Shutting down old tor.") + c.tor.Shutdown() + c.tor = nil + } + + // Apply the update. + log.Printf("update: Updating Tor Browser.") + async.UpdateProgress("Updating Tor Browser.") + + async.ToUI <- false // Lock out canceling. + + if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil { + log.Printf("update: Failed to apply update: %v", async.Err) + if patchType == patchPartial { + c.Cfg.SetSkipPartialUpdate(true) + if async.Err = c.Cfg.Sync(); async.Err != nil { + return + } + } + async.ToUI <- true // Unlock canceling. + continue + } + + // Failues past this point are catastrophic in that, the on-disk + // bundle is up to date, but the post-update tasks have failed. + + // Reinstall the autoconfig stuff. + if async.Err = writeAutoconfig(c.Cfg); async.Err != nil { + return + }
- async.ToUI <- true // Unlock canceling. + // Update the maniftest and config. + c.Manif.SetVersion(update.AppVersion) + if async.Err = c.Manif.Sync(); async.Err != nil { + return + } + c.Cfg.SetForceUpdate(false) + c.Cfg.SetSkipPartialUpdate(false) + if async.Err = c.Cfg.Sync(); async.Err != nil { + return + } + + async.ToUI <- true // Unlock canceling. + + // Restart tor if we launched it. + if !c.Cfg.UseSystemTor { + log.Printf("launch: Reconnecting to the Tor network.") + async.UpdateProgress("Reconnecting to the Tor network.") + _, async.Err = c.launchTor(async, false) + } + return + }
- // Restart tor if we launched it. - if !c.Cfg.UseSystemTor { - log.Printf("launch: Reconnecting to the Tor network.") - async.UpdateProgress("Reconnecting to the Tor network.") - _, async.Err = c.launchTor(async, false) + if nrAttempts == 0 { + async.Err = fmt.Errorf("no suitable MAR file found") + } else if async.Err != ErrCanceled { + async.Err = fmt.Errorf("failed to apply all possible MAR files") } return }