CVE-2015-3156
Description
The _write_config function in trove/guestagent/datastore/experimental/mongodb/service.py, reset_configuration function in trove/guestagent/datastore/experimental/postgresql/service/config.py, write_config function in trove/guestagent/datastore/experimental/redis/service.py, _write_mycnf function in trove/guestagent/datastore/mysql/service.py, InnoBackupEx::_run_prepare function in trove/guestagent/strategies/restore/mysql_impl.py, InnoBackupEx::cmd function in trove/guestagent/strategies/backup/mysql_impl.py, MySQLDump::cmd in trove/guestagent/strategies/backup/mysql_impl.py, InnoBackupExIncremental::cmd function in trove/guestagent/strategies/backup/mysql_impl.py, _get_actual_db_status function in trove/guestagent/datastore/experimental/cassandra/system.py and trove/guestagent/datastore/experimental/cassandra/service.py, and multiple class CbBackup methods in trove/guestagent/strategies/backup/experimental/couchbase_impl.py in Openstack DBaaS (aka Trove) as packaged in Openstack before 2015.1.0 (aka Kilo) allows local users to write to configuration files via a symlink attack on a temporary file.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
trovePyPI | < 4.0.0a0 | 4.0.0a0 |
Affected products
1Patches
161774984aa2bAddress predictable temp file vulnerability
2 files changed · +77 −27
trove/guestagent/datastore/cassandra/service.py+24 −11 modified@@ -14,6 +14,7 @@ # under the License. import os +import tempfile import yaml from trove.common import cfg from trove.common import utils @@ -117,22 +118,34 @@ def _install_db(self, packages): packager.pkg_install(packages, None, system.INSTALL_TIMEOUT) LOG.debug("Finished installing Cassandra server") - def write_config(self, config_contents): - LOG.debug('Defining temp config holder at %s.' % - system.CASSANDRA_TEMP_CONF) + def write_config(self, config_contents, + execute_function=utils.execute_with_timeout, + mkstemp_function=tempfile.mkstemp, + unlink_function=os.unlink): - try: - with open(system.CASSANDRA_TEMP_CONF, 'w+') as conf: - conf.write(config_contents) + # first securely create a temp file. mkstemp() will set + # os.O_EXCL on the open() call, and we get a file with + # permissions of 600 by default. + (conf_fd, conf_path) = mkstemp_function() - LOG.info(_('Writing new config.')) + LOG.debug('Storing temporary configuration at %s.' % conf_path) - utils.execute_with_timeout("sudo", "mv", - system.CASSANDRA_TEMP_CONF, - system.CASSANDRA_CONF) + # write config and close the file, delete it if there is an + # error. only unlink if there is a problem. In normal course, + # we move the file. + try: + os.write(conf_fd, config_contents) + execute_function("sudo", "mv", conf_path, system.CASSANDRA_CONF) except Exception: - os.unlink(system.CASSANDRA_TEMP_CONF) + LOG.exception( + _("Exception generating Cassandra configuration %s.") % + conf_path) + unlink_function(conf_path) raise + finally: + os.close(conf_fd) + + LOG.info(_('Wrote new Cassandra configuration.')) def read_conf(self): """Returns cassandra.yaml in dict structure."""
trove/tests/unittests/guestagent/test_dbaas.py+53 −16 modified@@ -13,6 +13,7 @@ # under the License. import os +import tempfile from uuid import uuid4 import time from mock import Mock @@ -39,6 +40,7 @@ from trove.guestagent.datastore.redis.service import RedisApp from trove.guestagent.datastore.redis import system as RedisSystem from trove.guestagent.datastore.cassandra import service as cass_service +from trove.guestagent.datastore.cassandra import system as cass_system from trove.guestagent.datastore.mysql.service import MySqlAdmin from trove.guestagent.datastore.mysql.service import MySqlRootAccess from trove.guestagent.datastore.mysql.service import MySqlApp @@ -1448,7 +1450,6 @@ def setUp(self): rd_instance.ServiceStatuses.NEW) self.cassandra = cass_service.CassandraApp(self.appStatus) self.orig_unlink = os.unlink - os.unlink = Mock() def tearDown(self): @@ -1459,7 +1460,6 @@ def tearDown(self): cass_service.packager.pkg_version = self.pkg_version cass_service.packager = self.pkg InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete() - os.unlink = self.orig_unlink def assert_reported_status(self, expected_status): service_status = InstanceServiceStatus.find_by( @@ -1574,27 +1574,64 @@ def test_install_install_error(self): self.assert_reported_status(rd_instance.ServiceStatuses.NEW) def test_cassandra_error_in_write_config_verify_unlink(self): + # this test verifies not only that the write_config + # method properly invoked execute, but also that it properly + # attempted to unlink the file (as a result of the exception) from trove.common.exception import ProcessExecutionError - cass_service.utils.execute_with_timeout = ( - Mock(side_effect=ProcessExecutionError('some exception'))) - configuration = 'this is my configuration' + execute_with_timeout = Mock( + side_effect=ProcessExecutionError('some exception')) - self.assertRaises(ProcessExecutionError, - self.cassandra.write_config, - config_contents=configuration) - self.assertEqual(cass_service.utils.execute_with_timeout.call_count, 1) - self.assertEqual(os.unlink.call_count, 1) + mock_unlink = Mock(return_value=0) + + # We call tempfile.mkstemp() here and Mock() the mkstemp() + # parameter to write_config for testability. + (temp_handle, temp_config_name) = tempfile.mkstemp() + mock_mkstemp = MagicMock(return_value=(temp_handle, temp_config_name)) - def test_cassandra_error_in_write_config(self): - from trove.common.exception import ProcessExecutionError - cass_service.utils.execute_with_timeout = ( - Mock(side_effect=ProcessExecutionError('some exception'))) configuration = 'this is my configuration' self.assertRaises(ProcessExecutionError, self.cassandra.write_config, - config_contents=configuration) - self.assertEqual(cass_service.utils.execute_with_timeout.call_count, 1) + config_contents=configuration, + execute_function=execute_with_timeout, + mkstemp_function=mock_mkstemp, + unlink_function=mock_unlink) + + self.assertEqual(mock_unlink.call_count, 1) + + # really delete the temporary_config_file + os.unlink(temp_config_name) + + def test_cassandra_write_config(self): + # ensure that write_config creates a temporary file, and then + # moves the file to the final place. Also validate the + # contents of the file written. + + # We call tempfile.mkstemp() here and Mock() the mkstemp() + # parameter to write_config for testability. + (temp_handle, temp_config_name) = tempfile.mkstemp() + mock_mkstemp = MagicMock(return_value=(temp_handle, temp_config_name)) + + configuration = 'some arbitrary configuration text' + + mock_execute = MagicMock(return_value=('', '')) + + self.cassandra.write_config(configuration, + execute_function=mock_execute, + mkstemp_function=mock_mkstemp) + + mock_execute.assert_called_with("sudo", "mv", + temp_config_name, + cass_system.CASSANDRA_CONF) + mock_mkstemp.assert_called_once() + + with open(temp_config_name, 'r') as config_file: + configuration_data = config_file.read() + + self.assertEqual(configuration, configuration_data) + + # really delete the temporary_config_file + os.unlink(temp_config_name) class CouchbaseAppTest(testtools.TestCase):
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
14- bugs.launchpad.net/trove/+bug/1398195nvdIssue TrackingThird Party AdvisoryVDB EntryWEB
- bugzilla.redhat.com/show_bug.cginvdIssue TrackingThird Party AdvisoryVDB EntryWEB
- github.com/advisories/GHSA-98c8-36p9-gw66ghsaADVISORY
- github.com/openstack/trove/blob/master/trove/guestagent/datastore/experimental/cassandra/service.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/datastore/experimental/mongodb/service.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/datastore/experimental/redis/service.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/datastore/mysql/service.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/strategies/backup/experimental/couchbase_impl.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/strategies/backup/mysql_impl.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/strategies/backup/mysql_impl.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/strategies/backup/mysql_impl.pynvdThird Party AdvisoryWEB
- github.com/openstack/trove/blob/master/trove/guestagent/strategies/restore/mysql_impl.pynvdThird Party AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2015-3156ghsaADVISORY
- github.com/openstack/trove/commit/61774984aa2bacfe89867fc39a402a6a4cfb8f33ghsaWEB
News mentions
0No linked articles in our index yet.