Merge lp:~spiv/bzr/bug-400535 into lp:~bzr/bzr/trunk-old

Proposed by Andrew Bennetts
Status: Merged
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/bug-400535
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 162 lines
To merge this branch: bzr merge lp:~spiv/bzr/bug-400535
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+9031@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

This patch fixes a serious bug in 1.16 and 1.17rc1. cmd_serve was refactored in
May to split out a “serve_bzr” function that takes care of creating a
ChrootServer for the specified directory and then starting a TCP or inet server
with that backing transport. Unfortunately, the refactoring introduced a bug
where the ChrootServer is ignored, and the plain LocalTransport is served
directly.

Thankfully, this doesn't appear to cause any security holes because invalid
paths like "../../../foo" are rejected by the smart server request handler
immediately, and requests that would try to walk up the filesystem to find a
BzrDir get stopped by the BzrDir.open jail that was recently introduced.

It's possible I've missed something and there is a way to exploit this bug to
trick an existing smart request into revealing data from outside the designated
directory, perhaps in conjunction with a server-side plugin, but so far I
haven't found any.

So, this patch fixes the essentially trivial bug, and adds a rather complicated
cmd_serve blackbox test that to prevent it regressing.

I suspect this bug also causes <https://bugs.launchpad.net/bzr/+bug/398199>, or
is at least contributing to it.

Because the bug is so serious, and because the fix is so obviously correct, this
fix should be included in 1.17 as well as bzr.dev.

-Andrew.

Revision history for this message
Robert Collins (lifeless) wrote :

 review +1

If backing_urls is in the local frame, then I suggest including it in
the new hook, if its an attribute on self, the the new hook signature is
fine.

Do we need to issue a new 1.16.x as well?

-Rob

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

Robert Collins wrote:
> Review: Approve
> review +1
>
> If backing_urls is in the local frame, then I suggest including it in
> the new hook, if its an attribute on self, the the new hook signature is
> fine.

It's derivable from the server_obj, but not particularly conveniently so. I'll
add it.

> Do we need to issue a new 1.16.x as well?

I think that would be good, although perhaps with 1.17 so near it's not such a
big deal? I guess it's up to the RM and those that would build the installers
if they think it's worth the effort. I'm wavering between being +1 and +0.5 on
issuing a new 1.16.x for this.

-Andrew.

Revision history for this message
Robert Collins (lifeless) wrote :

I'd err on the side of more-effort where security concerns are present.

-Rob

Revision history for this message
Martin Pool (mbp) wrote :

2009/7/20 Robert Collins <email address hidden>:
> I'd err on the side of more-effort where security concerns are present.

How about if we talk to eg package maintainers before making applying
that effort, and see if they actually want a 1.16.1, or if perhaps
they just want the relevant patch?

--
Martin <http://launchpad.net/~mbp/>

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, 2009-07-20 at 04:30 +0000, Martin Pool wrote:
> 2009/7/20 Robert Collins <email address hidden>:
> > I'd err on the side of more-effort where security concerns are present.
>
> How about if we talk to eg package maintainers before making applying
> that effort, and see if they actually want a 1.16.1, or if perhaps
> they just want the relevant patch?

Wearing my DD hat I'd want both - I can check that the new version is
really just the patch, then upload it without having to add a new patch
to the package which I'd just have to remove later.

-Rob

Revision history for this message
Wouter van Heyst (larstiq) wrote :

On Mon, Jul 20, 2009 at 01:10:17AM -0000, Andrew Bennetts wrote:
> Andrew Bennetts has proposed merging lp:~spiv/bzr/bug-400535 into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This patch fixes a serious bug in 1.16 and 1.17rc1. cmd_serve was refactored in
> May to split out a “serve_bzr” function that takes care of creating a
> ChrootServer for the specified directory and then starting a TCP or inet server
> with that backing transport. Unfortunately, the refactoring introduced a bug
> where the ChrootServer is ignored, and the plain LocalTransport is served
> directly.

Aha. I guess that explains why my smart-server locations.conf [chroot-*:///srv/bzr/]
section mysteriously stopped working with 1.16, and instead triggets on [/srv/bzr/].

I do think the latter one is much much nicer on the admin. Can I request
you retain the config behaviour for this merge, or do you want a
seperate bug for that?

Wouter van Heyst

Revision history for this message
Robert Collins (lifeless) wrote :

New bug please.

_Rob

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-07-17 05:32:50 +0000
3+++ NEWS 2009-07-20 07:35:31 +0000
4@@ -33,6 +33,9 @@
5 * ``bzr mv`` no longer takes out branch locks, which allows it to work
6 when the branch is readonly. (Robert Collins, #216541)
7
8+* ``bzr serve`` once again applies a ``ChrootServer`` to the given
9+ directory before serving it. (Andrew Bennetts, #400535)
10+
11 * Fixed spurious "Source branch does not support stacking" warning when
12 pushing. (Andrew Bennetts, #388908)
13
14
15=== modified file 'bzrlib/smart/server.py'
16--- bzrlib/smart/server.py 2009-06-10 03:56:49 +0000
17+++ bzrlib/smart/server.py 2009-07-20 07:35:31 +0000
18@@ -111,6 +111,8 @@
19 pass
20 for hook in SmartTCPServer.hooks['server_started']:
21 hook(backing_urls, self.get_url())
22+ for hook in SmartTCPServer.hooks['server_started_ex']:
23+ hook(backing_urls, self)
24 self._started.set()
25 try:
26 try:
27@@ -214,6 +216,10 @@
28 "where backing_url is a list of URLs giving the "
29 "server-specific directory locations, and public_url is the "
30 "public URL for the directory being served.", (0, 16), None))
31+ self.create_hook(HookPoint('server_started_ex',
32+ "Called by the bzr server when it starts serving a directory. "
33+ "server_started is called with (backing_urls, server_obj).",
34+ (1, 17), None))
35 self.create_hook(HookPoint('server_stopped',
36 "Called by the bzr server when it stops serving a directory. "
37 "server_stopped is called with the same parameters as the "
38@@ -313,7 +319,7 @@
39 from bzrlib.transport.chroot import ChrootServer
40 chroot_server = ChrootServer(transport)
41 chroot_server.setUp()
42- t = get_transport(chroot_server.get_url())
43+ transport = get_transport(chroot_server.get_url())
44 if inet:
45 smart_server = medium.SmartServerPipeStreamMedium(
46 sys.stdin, sys.stdout, transport)
47@@ -322,8 +328,7 @@
48 host = medium.BZR_DEFAULT_INTERFACE
49 if port is None:
50 port = medium.BZR_DEFAULT_PORT
51- smart_server = SmartTCPServer(
52- transport, host=host, port=port)
53+ smart_server = SmartTCPServer(transport, host=host, port=port)
54 trace.note('listening on port: %s' % smart_server.port)
55 # For the duration of this server, no UI output is permitted. note
56 # that this may cause problems with blackbox tests. This should be
57
58=== modified file 'bzrlib/tests/blackbox/test_serve.py'
59--- bzrlib/tests/blackbox/test_serve.py 2009-06-10 03:56:49 +0000
60+++ bzrlib/tests/blackbox/test_serve.py 2009-07-20 07:35:31 +0000
61@@ -21,18 +21,22 @@
62 import signal
63 import subprocess
64 import sys
65+import thread
66 import threading
67
68 from bzrlib import (
69 errors,
70 osutils,
71 revision as _mod_revision,
72+ transport,
73 )
74 from bzrlib.branch import Branch
75 from bzrlib.bzrdir import BzrDir
76 from bzrlib.errors import ParamikoNotPresent
77-from bzrlib.smart import medium
78+from bzrlib.smart import client, medium
79+from bzrlib.smart.server import SmartTCPServer
80 from bzrlib.tests import TestCaseWithTransport, TestSkipped
81+from bzrlib.trace import mutter
82 from bzrlib.transport import get_transport, remote
83
84
85@@ -239,3 +243,77 @@
86 % bzr_remote_path],
87 self.command_executed)
88
89+
90+class TestCmdServeChrooting(TestCaseWithTransport):
91+
92+ def test_serve_tcp(self):
93+ """'bzr serve' wraps the given --directory in a ChrootServer.
94+
95+ So requests that search up through the parent directories (like
96+ find_repositoryV3) will give "not found" responses, rather than
97+ InvalidURLJoin or jail break errors.
98+ """
99+ t = self.get_transport()
100+ t.mkdir('server-root')
101+ self.run_bzr_serve_then_func(
102+ ['--port', '0', '--directory', t.local_abspath('server-root'),
103+ '--allow-writes'],
104+ self.when_server_started)
105+ # The when_server_started method issued a find_repositoryV3 that should
106+ # fail with 'norepository' because there are no repositories inside the
107+ # --directory.
108+ self.assertEqual(('norepository',), self.client_resp)
109+
110+ def run_bzr_serve_then_func(self, serve_args, func, *func_args,
111+ **func_kwargs):
112+ """Run 'bzr serve', and run the given func in a thread once the server
113+ has started.
114+
115+ When 'func' terminates, the server will be terminated too.
116+ """
117+ # install hook
118+ def on_server_start(backing_urls, tcp_server):
119+ t = threading.Thread(
120+ target=on_server_start_thread, args=(tcp_server,))
121+ t.start()
122+ def on_server_start_thread(tcp_server):
123+ try:
124+ # Run func
125+ self.tcp_server = tcp_server
126+ try:
127+ func(*func_args, **func_kwargs)
128+ except Exception, e:
129+ # Log errors to make some test failures a little less
130+ # mysterious.
131+ mutter('func broke: %r', e)
132+ finally:
133+ # Then stop the server
134+ mutter('interrupting...')
135+ thread.interrupt_main()
136+ SmartTCPServer.hooks.install_named_hook(
137+ 'server_started_ex', on_server_start,
138+ 'run_bzr_serve_then_func hook')
139+ # start a TCP server
140+ try:
141+ self.run_bzr(['serve'] + list(serve_args))
142+ except KeyboardInterrupt:
143+ pass
144+
145+ def when_server_started(self):
146+ # Connect to the TCP server and issue some requests and see what comes
147+ # back.
148+ client_medium = medium.SmartTCPClientMedium(
149+ '127.0.0.1', self.tcp_server.port,
150+ 'bzr://localhost:%d/' % (self.tcp_server.port,))
151+ smart_client = client._SmartClient(client_medium)
152+ resp = smart_client.call('mkdir', 'foo', '')
153+ resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
154+ try:
155+ resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
156+ except errors.ErrorFromSmartServer, e:
157+ resp = e.error_tuple
158+ self.client_resp = resp
159+ client_medium.disconnect()
160+
161+
162+