VYPR
Medium severity5.5NVD Advisory· Published Aug 11, 2017· Updated May 13, 2026

CVE-2015-3156

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.

PackageAffected versionsPatched versions
trovePyPI
< 4.0.0a04.0.0a0

Affected products

1
  • cpe:2.3:a:openstack:trove:*:*:*:*:*:*:*:*
    Range: <=2014.2.4

Patches

1
61774984aa2b

Address predictable temp file vulnerability

https://github.com/openstack/troveAmrith KumarDec 4, 2014via ghsa
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

News mentions

0

No linked articles in our index yet.