Skip to content

Commit

Permalink
Prevent file injection writing to host filesystem.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ttx committed Jul 3, 2012
1 parent 33c2575 commit b0feaff
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 25 deletions.
20 changes: 20 additions & 0 deletions nova/tests/test_virt.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.

from nova import exception
from nova import flags
from nova import test
from nova.virt.disk import api as disk_api
from nova.virt import driver

FLAGS = flags.FLAGS
Expand Down Expand Up @@ -81,3 +83,21 @@ def test_swap_is_usable(self):
'swap_size': 0}))
self.assertTrue(driver.swap_is_usable({'device_name': '/dev/sdb',
'swap_size': 1}))


class TestVirtDisk(test.TestCase):
def test_check_safe_path(self):
ret = disk_api._join_and_check_path_within_fs('/foo', 'etc',
'something.conf')
self.assertEquals(ret, '/foo/etc/something.conf')

def test_check_unsafe_path(self):
self.assertRaises(exception.Invalid,
disk_api._join_and_check_path_within_fs,
'/foo', 'etc/../../../something.conf')

def test_inject_files_with_bad_path(self):
self.assertRaises(exception.Invalid,
disk_api._inject_file_into_fs,
'/tmp', '/etc/../../../../etc/passwd',
'hax')
71 changes: 46 additions & 25 deletions nova/virt/disk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,20 +306,39 @@ def inject_data_into_fs(fs, key, net, metadata, admin_password, execute):
_inject_admin_password_into_fs(admin_password, fs, execute=execute)


def _inject_file_into_fs(fs, path, contents):
absolute_path = os.path.join(fs, path.lstrip('/'))
def _join_and_check_path_within_fs(fs, *args):
'''os.path.join() with safety check for injected file paths.
Join the supplied path components and make sure that the
resulting path we are injecting into is within the
mounted guest fs. Trying to be clever and specifying a
path with '..' in it will hit this safeguard.
'''
absolute_path = os.path.realpath(os.path.join(fs, *args))
if not absolute_path.startswith(os.path.realpath(fs) + '/'):
raise exception.Invalid(_('injected file path not valid'))
return absolute_path


def _inject_file_into_fs(fs, path, contents, append=False):
absolute_path = _join_and_check_path_within_fs(fs, path.lstrip('/'))

parent_dir = os.path.dirname(absolute_path)
utils.execute('mkdir', '-p', parent_dir, run_as_root=True)
utils.execute('tee', absolute_path, process_input=contents,
run_as_root=True)

args = []
if append:
args.append('-a')
args.append(absolute_path)

kwargs = dict(process_input=contents, run_as_root=True)

utils.execute('tee', *args, **kwargs)


def _inject_metadata_into_fs(metadata, fs, execute=None):
metadata_path = os.path.join(fs, "meta.js")
metadata = dict([(m.key, m.value) for m in metadata])

utils.execute('tee', metadata_path,
process_input=json.dumps(metadata), run_as_root=True)
_inject_file_into_fs(fs, 'meta.js', json.dumps(metadata))


def _inject_key_into_fs(key, fs, execute=None):
Expand All @@ -328,33 +347,36 @@ def _inject_key_into_fs(key, fs, execute=None):
key is an ssh key string.
fs is the path to the base of the filesystem into which to inject the key.
"""
sshdir = os.path.join(fs, 'root', '.ssh')
sshdir = _join_and_check_path_within_fs(fs, 'root', '.ssh')
utils.execute('mkdir', '-p', sshdir, run_as_root=True)
utils.execute('chown', 'root', sshdir, run_as_root=True)
utils.execute('chmod', '700', sshdir, run_as_root=True)
keyfile = os.path.join(sshdir, 'authorized_keys')
key_data = [

keyfile = os.path.join('root', '.ssh', 'authorized_keys')

key_data = ''.join([
'\n',
'# The following ssh key was injected by Nova',
'\n',
key.strip(),
'\n',
]
utils.execute('tee', '-a', keyfile,
process_input=''.join(key_data), run_as_root=True)
])

_inject_file_into_fs(fs, keyfile, key_data, append=True)


def _inject_net_into_fs(net, fs, execute=None):
"""Inject /etc/network/interfaces into the filesystem rooted at fs.
net is the contents of /etc/network/interfaces.
"""
netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
netdir = _join_and_check_path_within_fs(fs, 'etc', 'network')
utils.execute('mkdir', '-p', netdir, run_as_root=True)
utils.execute('chown', 'root:root', netdir, run_as_root=True)
utils.execute('chmod', 755, netdir, run_as_root=True)
netfile = os.path.join(netdir, 'interfaces')
utils.execute('tee', netfile, process_input=net, run_as_root=True)

netfile = os.path.join('etc', 'network', 'interfaces')
_inject_file_into_fs(fs, netfile, net)


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

utils.execute('cp', os.path.join(fs, 'etc', 'passwd'), tmp_passwd,
run_as_root=True)
utils.execute('cp', os.path.join(fs, 'etc', 'shadow'), tmp_shadow,
run_as_root=True)
passwd_path = _join_and_check_path_within_fs(fs, 'etc', 'passwd')
shadow_path = _join_and_check_path_within_fs(fs, 'etc', 'shadow')

utils.execute('cp', passwd_path, tmp_passwd, run_as_root=True)
utils.execute('cp', shadow_path, tmp_shadow, run_as_root=True)
_set_passwd(admin_user, admin_passwd, tmp_passwd, tmp_shadow)
utils.execute('cp', tmp_passwd, os.path.join(fs, 'etc', 'passwd'),
run_as_root=True)
utils.execute('cp', tmp_passwd, passwd_path, run_as_root=True)
os.unlink(tmp_passwd)
utils.execute('cp', tmp_shadow, os.path.join(fs, 'etc', 'shadow'),
run_as_root=True)
utils.execute('cp', tmp_shadow, shadow_path, run_as_root=True)
os.unlink(tmp_shadow)


Expand Down

0 comments on commit b0feaff

Please sign in to comment.