[tor-commits] [sandboxed-tor-browser/master] Bug 20851: If the incremental update fails, fall back to the complete update.

yawning at torproject.org yawning at torproject.org
Wed Dec 28 04:44:16 UTC 2016


commit 3b5281a4191efd5336328932fbe74e17e9eb7614
Author: Yawning Angel <yawning at 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
 }





More information about the tor-commits mailing list