Skip to content

Commit

Permalink
Avoid the possibility of truncating disk info file
Browse files Browse the repository at this point in the history
Commit dc8de42 makes nova persist image format to a file to avoid
attacks based on changing it later. However the way it was implemented
leaves a small window of opportunity for the file to be truncated before
it gets written back to effectively making it possible for data to get
lost leaving us with a potential problem next time it is attempted to be
read.

This patch changes the way file is updated to be atomic, thus closing
the race window (and also removes the chown that we did not really
need).

It is worth noting that a better solution to this would be
to allow the code calling the imagebackend to write the file (once!)
and make it impossible to update after the boot process is done. This
approach would require more refactoring of the libvirt driver code, and
may be done in the future.

Partial-bug: #1221190
Change-Id: Ia1b073f38e096989f34d1774a12a1b4151773fc7
  • Loading branch information
djipko authored and kk7ds committed Apr 9, 2014
1 parent aa3ff66 commit d416f43
Show file tree
Hide file tree
Showing 4 changed files with 13 additions and 48 deletions.
1 change: 0 additions & 1 deletion etc/nova/rootwrap.d/compute.filters
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ mkdir: CommandFilter, mkdir, root
# nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
# nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
# nova/virt/libvirt/connection.py: 'chown', 'root', basepath('disk')
# nova/utils.py: 'chown', owner_uid, path
chown: CommandFilter, chown, root

# nova/virt/disk/vfs/localfs.py: 'chmod'
Expand Down
21 changes: 0 additions & 21 deletions nova/tests/virt/libvirt/test_imagebackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from nova import test
from nova.tests import fake_processutils
from nova.tests.virt.libvirt import fake_libvirt_utils
from nova import utils
from nova.virt.libvirt import imagebackend

CONF = cfg.CONF
Expand Down Expand Up @@ -67,10 +66,6 @@ def setUp(self):
'nova.virt.libvirt.imagebackend.libvirt_utils',
fake_libvirt_utils))

def fake_chown(path, owner_uid=None):
return None
self.stubs.Set(utils, 'chown', fake_chown)

def tearDown(self):
super(_ImageTestCase, self).tearDown()
shutil.rmtree(self.INSTANCES_PATH)
Expand Down Expand Up @@ -127,10 +122,6 @@ def setUp(self):
super(RawTestCase, self).setUp()
self.stubs.Set(imagebackend.Raw, 'correct_format', lambda _: None)

def fake_chown(path, owner_uid=None):
return None
self.stubs.Set(utils, 'chown', fake_chown)

def prepare_mocks(self):
fn = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(imagebackend.utils.synchronized,
Expand Down Expand Up @@ -243,10 +234,6 @@ def test_correct_format(self):
self.mox.StubOutWithMock(os.path, 'exists')
self.mox.StubOutWithMock(imagebackend.images, 'qemu_img_info')

def fake_chown(path, owner_uid=None):
return None
self.stubs.Set(utils, 'chown', fake_chown)

os.path.exists(self.PATH).AndReturn(True)
os.path.exists(self.DISK_INFO_PATH).AndReturn(False)
info = self.mox.CreateMockAnything()
Expand Down Expand Up @@ -275,10 +262,6 @@ def setUp(self):
self.QCOW2_BASE = (self.TEMPLATE_PATH +
'_%d' % (self.SIZE / units.Gi))

def fake_chown(path, owner_uid=None):
return None
self.stubs.Set(utils, 'chown', fake_chown)

def prepare_mocks(self):
fn = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(imagebackend.utils.synchronized,
Expand Down Expand Up @@ -874,10 +857,6 @@ class BackendTestCase(test.NoDBTestCase):
def setUp(self):
super(BackendTestCase, self).setUp()

def fake_chown(path, owner_uid=None):
return None
self.stubs.Set(utils, 'chown', fake_chown)

def get_image(self, use_cow, image_type):
return imagebackend.Backend(use_cow).image(self.INSTANCE,
self.NAME,
Expand Down
14 changes: 0 additions & 14 deletions nova/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,20 +761,6 @@ def temporary_chown(path, owner_uid=None):
execute('chown', orig_uid, path, run_as_root=True)


def chown(path, owner_uid=None):
"""chown a path.
:param owner_uid: UID of owner (defaults to current user)
"""
if owner_uid is None:
owner_uid = os.getuid()

orig_uid = os.stat(path).st_uid

if orig_uid != owner_uid:
execute('chown', owner_uid, path, run_as_root=True)


@contextlib.contextmanager
def tempdir(**kwargs):
argdict = kwargs.copy()
Expand Down
25 changes: 13 additions & 12 deletions nova/virt/libvirt/imagebackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,20 +272,21 @@ def _dict_from_line(line):
lock_path=self.lock_path)
def write_to_disk_info_file():
# Use os.open to create it without group or world write permission.
fd = os.open(self.disk_info_path, os.O_RDWR | os.O_CREAT, 0o644)
with os.fdopen(fd, "r+") as disk_info_file:
fd = os.open(self.disk_info_path, os.O_RDONLY | os.O_CREAT, 0o644)
with os.fdopen(fd, "r") as disk_info_file:
line = disk_info_file.read().rstrip()
dct = _dict_from_line(line)
if self.path in dct:
msg = _("Attempted overwrite of an existing value.")
raise exception.InvalidDiskInfo(reason=msg)
dct.update({self.path: driver_format})
disk_info_file.seek(0)
disk_info_file.truncate()
disk_info_file.write('%s\n' % jsonutils.dumps(dct))
# Ensure the file is always owned by the nova user so qemu can't
# write it.
utils.chown(self.disk_info_path, owner_uid=os.getuid())

if self.path in dct:
msg = _("Attempted overwrite of an existing value.")
raise exception.InvalidDiskInfo(reason=msg)
dct.update({self.path: driver_format})

tmp_path = self.disk_info_path + ".tmp"
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT, 0o644)
with os.fdopen(fd, "w") as tmp_file:
tmp_file.write('%s\n' % jsonutils.dumps(dct))
os.rename(tmp_path, self.disk_info_path)

try:
if (self.disk_info_path is not None and
Expand Down

0 comments on commit d416f43

Please sign in to comment.