[PATCH 1 of 2] streamclone: consider secret changesets (BC) (issue5589)

Gregory Szorc gregory.szorc at gmail.com
Fri Jun 9 14:22:10 EDT 2017


On Fri, Jun 9, 2017 at 10:49 AM, Gregory Szorc <gregory.szorc at gmail.com>
wrote:

> # HG changeset patch
> # User Gregory Szorc <gregory.szorc at gmail.com>
> # Date 1497030073 25200
> #      Fri Jun 09 10:41:13 2017 -0700
> # Node ID 9e4e3022c76912d5d0efd8b93b90854d0abbfbbf
> # Parent  326c0e2c1a1d59e07f4c9d86f81e4419c3d779d8
> streamclone: consider secret changesets (BC) (issue5589)
>
> Previously, a repo containing secret changesets would be served via
> stream clone, transferring those secret changesets. While secret
> changesets aren't meant to imply strong security (if you really
> want to keep them secret, others shouldn't have read access to the
> repo), we should at least make an effort to protect secret changesets
> when possible.
>
> After this commit, we no longer serve stream clones for repos
> containing secret changesets by default. This is backwards
> incompatible behavior. In case anyone is relying on the behavior,
> we provide a config option to opt into the old behavior.
>
> Note that this defense is only beneficial for remote repos
> accessed via the wire protocol: if a client has access to the
> files backing a repo, they can get to the raw data and see secret
> revisions.
>
> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
> --- a/mercurial/help/config.txt
> +++ b/mercurial/help/config.txt
> @@ -1658,6 +1658,10 @@ Controls generic server settings.
>      the write lock while determining what data to transfer.
>      (default: True)
>
> +``uncompressedallowsecret``
> +    Whether to allow stream clones when the repository contains secret
> +    changesets. (default: False)
> +
>  ``preferuncompressed``
>      When set, clients will try to use the uncompressed streaming
>      protocol. (default: False)
> diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py
> --- a/mercurial/streamclone.py
> +++ b/mercurial/streamclone.py
> @@ -13,6 +13,7 @@ from .i18n import _
>  from . import (
>      branchmap,
>      error,
> +    phases,
>      store,
>      util,
>  )
> @@ -162,9 +163,18 @@ def maybeperformlegacystreamclone(pullop
>
>          repo.invalidate()
>
> -def allowservergeneration(ui):
> +def allowservergeneration(repo):
>      """Whether streaming clones are allowed from the server."""
> -    return ui.configbool('server', 'uncompressed', True, untrusted=True)
> +    if not repo.ui.configbool('server', 'uncompressed', True,
> untrusted=True):
> +        return False
> +
> +    # The way stream clone works makes it impossible to hide secret
> changesets.
> +    # So don't allow this by default.
> +    secret = phases.hassecret(repo)
> +    if secret:
> +        return repo.ui.configbool('server', 'uncompressedallowsecret',
> False)
> +
> +    return True
>
>  # This is it's own function so extensions can override it.
>  def _walkstreamfiles(repo):
> diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
> --- a/mercurial/wireproto.py
> +++ b/mercurial/wireproto.py
> @@ -754,7 +754,7 @@ def _capabilities(repo, proto):
>      """
>      # copy to prevent modification of the global list
>      caps = list(wireprotocaps)
> -    if streamclone.allowservergeneration(repo.ui):
> +    if streamclone.allowservergeneration(repo):
>          if repo.ui.configbool('server', 'preferuncompressed', False):
>              caps.append('stream-preferred')
>          requiredformats = repo.requirements & repo.supportedformats
> @@ -946,7 +946,7 @@ def stream(repo, proto):
>      capability with a value representing the version and flags of the repo
>      it is serving. Client checks to see if it understands the format.
>      '''
> -    if not streamclone.allowservergeneration(repo.ui):
> +    if not streamclone.allowservergeneration(repo):
>          return '1\n'
>
>      def getstream(it):
> diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-
> uncompressed.t
> --- a/tests/test-clone-uncompressed.t
> +++ b/tests/test-clone-uncompressed.t
> @@ -49,6 +49,77 @@ Clone with background file closing enabl
>    bundle2-input-bundle: 1 parts total
>    checking for updated bookmarks
>
> +Cannot stream clone when there are secret changesets
> +
> +  $ hg -R server phase --force --secret -r tip
> +  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-denied
> +  warning: stream clone requested but server has them disabled
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
> +
> +  $ killdaemons.py
> +
> +Streaming of secrets can be overridden by server config
> +
> +  $ cd server
> +  $ hg --config server.uncompressedallowsecret=true serve -p $HGPORT -d
> --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ cd ..
> +
> +  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-allowed
> +  streaming all changes
> +  1027 files to transfer, 96.3 KB of data
> +  transferred 96.3 KB in * seconds (*/sec) (glob)
> +  searching for changes
> +  no changes found
> +
> +  $ killdaemons.py
> +
> +Verify interaction between preferuncompressed and secret presence
> +
> +  $ cd server
> +  $ hg --config server.preferuncompressed=true serve -p $HGPORT -d
> --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ cd ..
> +
> +  $ hg clone -U http://localhost:$HGPORT preferuncompressed-secret
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
> +
> +  $ killdaemons.py
> +
> +Clone not allowed when full bundles disabled and can't serve secrets
> +
> +  $ cd server
> +  $ hg --config server.disablefullbundle=true serve -p $HGPORT -d
> --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ cd ..
> +
> +  $ hg clone --uncompressed http://localhost:$HGPORT secret-full-disabled
> +  warning: stream clone requested but server has them disabled
> +  requesting all changes
> +  remote: abort: server has pull-based clones disabled
> +  abort: pull failed on remote
> +  (remove --pull if specified or upgrade Mercurial)
> +  [255]
> +
> +Local stream clone with secrets involved
> +(This is just a test over behavior: if you have access to the repo's
> files,
> +there is no security so it isn't important to prevent a clone here.)
> +
> +  $ hg clone -U --uncompressed server local-secret
> +  warning: stream clone requested but server has them disabled
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
>
>  Stream clone while repo is changing:
>
> @@ -75,7 +146,7 @@ prepare repo with small and big file to
>    $ $TESTDIR/seq.py 50000 > repo/f2
>    $ hg -R repo ci -Aqm "0"
>    $ hg -R repo serve -p $HGPORT1 -d --pid-file=hg.pid --config
> extensions.delayer=delayer.py
> -  $ cat hg.pid >> $DAEMON_PIDS
> +  $ cat hg.pid > $DAEMON_PIDS
>

I changed this but forgot a killdaemons.py after the last test. Could you
please revert this to >> in flight?


>
>  clone while modifying the repo between stating file with write lock and
>  actually serving file content
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.mercurial-scm.org/pipermail/mercurial-devel/attachments/20170609/56aed7d0/attachment.html>


More information about the Mercurial-devel mailing list