Merge ~chad.smith/cloud-init:tests-fix-root-os-access-leak into cloud-init:master

Proposed by Chad Smith on 2017-09-14
Status: Merged
Approved by: Scott Moser on 2017-09-15
Approved revision: e19de87fdae9fb58d39ac415b73fa37e6b6e1e83
Merged at revision: 024ecc1f758d36cc0aa5ebce65704eed6bd66d45
Proposed branch: ~chad.smith/cloud-init:tests-fix-root-os-access-leak
Merge into: cloud-init:master
Diff against target: 170 lines (+26/-58)
2 files modified
cloudinit/config/cc_resizefs.py (+4/-8)
tests/unittests/test_handler/test_handler_resizefs.py (+22/-50)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve on 2017-09-15
Scott Moser 2017-09-14 Approve on 2017-09-15
Review via email: mp+330774@code.launchpad.net

Commit Message

resizefs: Drop check for read-only device file, do not warn on overlayroot.

As root user, os.access(<path>, os.W_OK) will always return True so that
path will never get executed. Also avoid a warning if the root is
overlayroot, which is the common case on a MAAS booted 'ephemeral' system.

Description of the Change

resizefs: Drop check and tests for read-only device file

As root user, os.access(<path>, os.W_OK) will always return True so that path will never get executed as Scott pointed out. Also, on a real read-only iscsi devpath, resizefs doesn't perform an operation on a filesystem it recognizes as read-only. It may emit a warning, but not cause a traceback.

To post a comment you must log in.
Scott Moser (smoser) wrote :

So did this just expose an *actual* bug ?

  "root user still sees read-only files as writable so the unittests would fail"

If root user sees read-only files as writable, and cloud-init runs as root,
would we ever take that path ?

PASSED: Continuous integration, rev:675996d25dc2f7a101e75e6562d7d7e712e76f07
https://jenkins.ubuntu.com/server/job/cloud-init-ci/300/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/300/rebuild

review: Approve (continuous-integration)
Chad Smith (chad.smith) wrote :

I'm not quite sure what we should do here. You are right that root user will likely never see os.access(W_OK) as False because root's a privileged user so the perms don't matter.

Also, it seems that in containers we fail to resize up at cc_resizefs line 197 on ENOENT so generally trying to account for is_container checks below in the os.access and stat.S_ISBLK
conditionals feel like wasted logic. We could just check mode values instead of os.access like this:

os.stat(devpath).st_mode & (stat.S_IROTH | stat.S_IRGRP| stat.S_IRUSR)
I know there must be a better way here.

But ultimately, do you know if there are device files that would be read only for root?

1367b2b... by Chad Smith on 2017-09-14

drop 'not os.access(os.W_OK)' check from resizefs because os.access as root will always return writable and resizefs of read-only devices will perform no resize operation and exit

PASSED: Continuous integration, rev:1367b2b911d61ae95c2c635259b2f2b85c8eb541
https://jenkins.ubuntu.com/server/job/cloud-init-ci/301/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/301/rebuild

review: Approve (continuous-integration)
e19de87... by Chad Smith on 2017-09-15

log a message on overlayroot devpath and return False. Add unit test

Scott Moser (smoser) wrote :

Approve.
I've re-worded commit message some and am pushing after a tox

review: Approve

PASSED: Continuous integration, rev:e19de87fdae9fb58d39ac415b73fa37e6b6e1e83
https://jenkins.ubuntu.com/server/job/cloud-init-ci/312/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/312/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
2index f42b6a6..f774baa 100644
3--- a/cloudinit/config/cc_resizefs.py
4+++ b/cloudinit/config/cc_resizefs.py
5@@ -192,6 +192,10 @@ def is_device_path_writable_block(devpath, info, log):
6 return False
7 log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath)
8
9+ if devpath == 'overlayroot':
10+ log.debug("Not attempting to resize devpath '%s': %s", devpath, info)
11+ return False
12+
13 try:
14 statret = os.stat(devpath)
15 except OSError as exc:
16@@ -205,14 +209,6 @@ def is_device_path_writable_block(devpath, info, log):
17 raise exc
18 return False
19
20- if not os.access(devpath, os.W_OK):
21- if container:
22- log.debug("'%s' not writable in container. cannot resize: %s",
23- devpath, info)
24- else:
25- log.warn("'%s' not writable. cannot resize: %s", devpath, info)
26- return
27-
28 if not stat.S_ISBLK(statret.st_mode) and not stat.S_ISCHR(statret.st_mode):
29 if container:
30 log.debug("device '%s' not a block device in container."
31diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py
32index 76dddbf..3e5d436 100644
33--- a/tests/unittests/test_handler/test_handler_resizefs.py
34+++ b/tests/unittests/test_handler/test_handler_resizefs.py
35@@ -166,6 +166,18 @@ class TestIsDevicePathWritableBlock(CiTestCase):
36
37 with_logs = True
38
39+ def test_is_device_path_writable_block_false_on_overlayroot(self):
40+ """When devpath is overlayroot (on MAAS), is_dev_writable is False."""
41+ info = 'does not matter'
42+ is_writable = wrap_and_call(
43+ 'cloudinit.config.cc_resizefs.util',
44+ {'is_container': {'return_value': False}},
45+ is_device_path_writable_block, 'overlayroot', info, LOG)
46+ self.assertFalse(is_writable)
47+ self.assertIn(
48+ "Not attempting to resize devpath 'overlayroot'",
49+ self.logs.getvalue())
50+
51 def test_is_device_path_writable_block_warns_missing_cmdline_root(self):
52 """When root does not exist isn't in the cmdline, log warning."""
53 info = 'does not matter'
54@@ -178,24 +190,24 @@ class TestIsDevicePathWritableBlock(CiTestCase):
55 exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists'
56 with mock.patch(exists_mock_path) as m_exists:
57 m_exists.return_value = False
58- is_valid = wrap_and_call(
59+ is_writable = wrap_and_call(
60 'cloudinit.config.cc_resizefs.util',
61 {'is_container': {'return_value': False},
62 'get_mount_info': {'side_effect': fake_mount_info},
63 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}},
64 is_device_path_writable_block, '/dev/root', info, LOG)
65- self.assertFalse(is_valid)
66+ self.assertFalse(is_writable)
67 logs = self.logs.getvalue()
68 self.assertIn("WARNING: Unable to find device '/dev/root'", logs)
69
70 def test_is_device_path_writable_block_does_not_exist(self):
71 """When devpath does not exist, a warning is logged."""
72 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'
73- is_valid = wrap_and_call(
74+ is_writable = wrap_and_call(
75 'cloudinit.config.cc_resizefs.util',
76 {'is_container': {'return_value': False}},
77 is_device_path_writable_block, '/I/dont/exist', info, LOG)
78- self.assertFalse(is_valid)
79+ self.assertFalse(is_writable)
80 self.assertIn(
81 "WARNING: Device '/I/dont/exist' did not exist."
82 ' cannot resize: %s' % info,
83@@ -204,11 +216,11 @@ class TestIsDevicePathWritableBlock(CiTestCase):
84 def test_is_device_path_writable_block_does_not_exist_in_container(self):
85 """When devpath does not exist in a container, log a debug message."""
86 info = 'dev=/I/dont/exist mnt_point=/ path=/dev/none'
87- is_valid = wrap_and_call(
88+ is_writable = wrap_and_call(
89 'cloudinit.config.cc_resizefs.util',
90 {'is_container': {'return_value': True}},
91 is_device_path_writable_block, '/I/dont/exist', info, LOG)
92- self.assertFalse(is_valid)
93+ self.assertFalse(is_writable)
94 self.assertIn(
95 "DEBUG: Device '/I/dont/exist' did not exist in container."
96 ' cannot resize: %s' % info,
97@@ -226,57 +238,17 @@ class TestIsDevicePathWritableBlock(CiTestCase):
98 self.assertEqual(
99 'Something unexpected', str(context_manager.exception))
100
101- def test_is_device_path_writable_block_readonly(self):
102- """When root device is readonly, emit a warning and return False."""
103- fake_devpath = self.tmp_path('dev/readonly')
104- util.write_file(fake_devpath, '', mode=0o400) # read-only
105- info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
106-
107- exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists'
108- with mock.patch(exists_mock_path) as m_exists:
109- m_exists.return_value = False
110- is_valid = wrap_and_call(
111- 'cloudinit.config.cc_resizefs.util',
112- {'is_container': {'return_value': False},
113- 'rootdev_from_cmdline': {'return_value': fake_devpath}},
114- is_device_path_writable_block, '/dev/root', info, LOG)
115- self.assertFalse(is_valid)
116- logs = self.logs.getvalue()
117- self.assertIn(
118- "Converted /dev/root to '{0}' per kernel cmdline".format(
119- fake_devpath),
120- logs)
121- self.assertIn(
122- "WARNING: '{0}' not writable. cannot resize".format(fake_devpath),
123- logs)
124-
125- def test_is_device_path_writable_block_readonly_in_container(self):
126- """When root device is readonly, emit debug log and return False."""
127- fake_devpath = self.tmp_path('dev/readonly')
128- util.write_file(fake_devpath, '', mode=0o400) # read-only
129- info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
130-
131- is_valid = wrap_and_call(
132- 'cloudinit.config.cc_resizefs.util',
133- {'is_container': {'return_value': True}},
134- is_device_path_writable_block, fake_devpath, info, LOG)
135- self.assertFalse(is_valid)
136- self.assertIn(
137- "DEBUG: '{0}' not writable in container. cannot resize".format(
138- fake_devpath),
139- self.logs.getvalue())
140-
141 def test_is_device_path_writable_block_non_block(self):
142 """When device is not a block device, emit warning return False."""
143 fake_devpath = self.tmp_path('dev/readwrite')
144 util.write_file(fake_devpath, '', mode=0o600) # read-write
145 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
146
147- is_valid = wrap_and_call(
148+ is_writable = wrap_and_call(
149 'cloudinit.config.cc_resizefs.util',
150 {'is_container': {'return_value': False}},
151 is_device_path_writable_block, fake_devpath, info, LOG)
152- self.assertFalse(is_valid)
153+ self.assertFalse(is_writable)
154 self.assertIn(
155 "WARNING: device '{0}' not a block device. cannot resize".format(
156 fake_devpath),
157@@ -288,11 +260,11 @@ class TestIsDevicePathWritableBlock(CiTestCase):
158 util.write_file(fake_devpath, '', mode=0o600) # read-write
159 info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath)
160
161- is_valid = wrap_and_call(
162+ is_writable = wrap_and_call(
163 'cloudinit.config.cc_resizefs.util',
164 {'is_container': {'return_value': True}},
165 is_device_path_writable_block, fake_devpath, info, LOG)
166- self.assertFalse(is_valid)
167+ self.assertFalse(is_writable)
168 self.assertIn(
169 "DEBUG: device '{0}' not a block device in container."
170 ' cannot resize'.format(fake_devpath),

Subscribers

People subscribed via source and target branches