Skip to content

Commit b0feaff

Browse files
committed
Prevent file injection writing to host filesystem.
Fix bug 1015531, CVE-2012-3360, CVE-2012-3361 This patch prevents the file injection code from writing into the host filesystem if a user specifies a path for the injected file that contains '..'. The check is to make sure that the final normalized path that is about to be written to is within the mounted guest filesystem. This is a backport of Russell Bryant, Pádraig Brady and Mark McLoughlin's Folsom patch. Change-Id: Id8bd6ffb4d878467ba2086d341fce23f2ff5aa0a
1 parent 33c2575 commit b0feaff

File tree

2 files changed

+66
-25
lines changed

2 files changed

+66
-25
lines changed

nova/tests/test_virt.py

+20
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
# License for the specific language governing permissions and limitations
1616
# under the License.
1717

18+
from nova import exception
1819
from nova import flags
1920
from nova import test
21+
from nova.virt.disk import api as disk_api
2022
from nova.virt import driver
2123

2224
FLAGS = flags.FLAGS
@@ -81,3 +83,21 @@ def test_swap_is_usable(self):
8183
'swap_size': 0}))
8284
self.assertTrue(driver.swap_is_usable({'device_name': '/dev/sdb',
8385
'swap_size': 1}))
86+
87+
88+
class TestVirtDisk(test.TestCase):
89+
def test_check_safe_path(self):
90+
ret = disk_api._join_and_check_path_within_fs('/foo', 'etc',
91+
'something.conf')
92+
self.assertEquals(ret, '/foo/etc/something.conf')
93+
94+
def test_check_unsafe_path(self):
95+
self.assertRaises(exception.Invalid,
96+
disk_api._join_and_check_path_within_fs,
97+
'/foo', 'etc/../../../something.conf')
98+
99+
def test_inject_files_with_bad_path(self):
100+
self.assertRaises(exception.Invalid,
101+
disk_api._inject_file_into_fs,
102+
'/tmp', '/etc/../../../../etc/passwd',
103+
'hax')

nova/virt/disk/api.py

+46-25
Original file line numberDiff line numberDiff line change
@@ -306,20 +306,39 @@ def inject_data_into_fs(fs, key, net, metadata, admin_password, execute):
306306
_inject_admin_password_into_fs(admin_password, fs, execute=execute)
307307

308308

309-
def _inject_file_into_fs(fs, path, contents):
310-
absolute_path = os.path.join(fs, path.lstrip('/'))
309+
def _join_and_check_path_within_fs(fs, *args):
310+
'''os.path.join() with safety check for injected file paths.
311+
312+
Join the supplied path components and make sure that the
313+
resulting path we are injecting into is within the
314+
mounted guest fs. Trying to be clever and specifying a
315+
path with '..' in it will hit this safeguard.
316+
'''
317+
absolute_path = os.path.realpath(os.path.join(fs, *args))
318+
if not absolute_path.startswith(os.path.realpath(fs) + '/'):
319+
raise exception.Invalid(_('injected file path not valid'))
320+
return absolute_path
321+
322+
323+
def _inject_file_into_fs(fs, path, contents, append=False):
324+
absolute_path = _join_and_check_path_within_fs(fs, path.lstrip('/'))
325+
311326
parent_dir = os.path.dirname(absolute_path)
312327
utils.execute('mkdir', '-p', parent_dir, run_as_root=True)
313-
utils.execute('tee', absolute_path, process_input=contents,
314-
run_as_root=True)
328+
329+
args = []
330+
if append:
331+
args.append('-a')
332+
args.append(absolute_path)
333+
334+
kwargs = dict(process_input=contents, run_as_root=True)
335+
336+
utils.execute('tee', *args, **kwargs)
315337

316338

317339
def _inject_metadata_into_fs(metadata, fs, execute=None):
318-
metadata_path = os.path.join(fs, "meta.js")
319340
metadata = dict([(m.key, m.value) for m in metadata])
320-
321-
utils.execute('tee', metadata_path,
322-
process_input=json.dumps(metadata), run_as_root=True)
341+
_inject_file_into_fs(fs, 'meta.js', json.dumps(metadata))
323342

324343

325344
def _inject_key_into_fs(key, fs, execute=None):
@@ -328,33 +347,36 @@ def _inject_key_into_fs(key, fs, execute=None):
328347
key is an ssh key string.
329348
fs is the path to the base of the filesystem into which to inject the key.
330349
"""
331-
sshdir = os.path.join(fs, 'root', '.ssh')
350+
sshdir = _join_and_check_path_within_fs(fs, 'root', '.ssh')
332351
utils.execute('mkdir', '-p', sshdir, run_as_root=True)
333352
utils.execute('chown', 'root', sshdir, run_as_root=True)
334353
utils.execute('chmod', '700', sshdir, run_as_root=True)
335-
keyfile = os.path.join(sshdir, 'authorized_keys')
336-
key_data = [
354+
355+
keyfile = os.path.join('root', '.ssh', 'authorized_keys')
356+
357+
key_data = ''.join([
337358
'\n',
338359
'# The following ssh key was injected by Nova',
339360
'\n',
340361
key.strip(),
341362
'\n',
342-
]
343-
utils.execute('tee', '-a', keyfile,
344-
process_input=''.join(key_data), run_as_root=True)
363+
])
364+
365+
_inject_file_into_fs(fs, keyfile, key_data, append=True)
345366

346367

347368
def _inject_net_into_fs(net, fs, execute=None):
348369
"""Inject /etc/network/interfaces into the filesystem rooted at fs.
349370
350371
net is the contents of /etc/network/interfaces.
351372
"""
352-
netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
373+
netdir = _join_and_check_path_within_fs(fs, 'etc', 'network')
353374
utils.execute('mkdir', '-p', netdir, run_as_root=True)
354375
utils.execute('chown', 'root:root', netdir, run_as_root=True)
355376
utils.execute('chmod', 755, netdir, run_as_root=True)
356-
netfile = os.path.join(netdir, 'interfaces')
357-
utils.execute('tee', netfile, process_input=net, run_as_root=True)
377+
378+
netfile = os.path.join('etc', 'network', 'interfaces')
379+
_inject_file_into_fs(fs, netfile, net)
358380

359381

360382
def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
@@ -379,16 +401,15 @@ def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
379401
fd, tmp_shadow = tempfile.mkstemp()
380402
os.close(fd)
381403

382-
utils.execute('cp', os.path.join(fs, 'etc', 'passwd'), tmp_passwd,
383-
run_as_root=True)
384-
utils.execute('cp', os.path.join(fs, 'etc', 'shadow'), tmp_shadow,
385-
run_as_root=True)
404+
passwd_path = _join_and_check_path_within_fs(fs, 'etc', 'passwd')
405+
shadow_path = _join_and_check_path_within_fs(fs, 'etc', 'shadow')
406+
407+
utils.execute('cp', passwd_path, tmp_passwd, run_as_root=True)
408+
utils.execute('cp', shadow_path, tmp_shadow, run_as_root=True)
386409
_set_passwd(admin_user, admin_passwd, tmp_passwd, tmp_shadow)
387-
utils.execute('cp', tmp_passwd, os.path.join(fs, 'etc', 'passwd'),
388-
run_as_root=True)
410+
utils.execute('cp', tmp_passwd, passwd_path, run_as_root=True)
389411
os.unlink(tmp_passwd)
390-
utils.execute('cp', tmp_shadow, os.path.join(fs, 'etc', 'shadow'),
391-
run_as_root=True)
412+
utils.execute('cp', tmp_shadow, shadow_path, run_as_root=True)
392413
os.unlink(tmp_shadow)
393414

394415

0 commit comments

Comments
 (0)