CVE-2012-4406 (Python, published 2012-10-22T23:55Z)
Description: value: OpenStack Object Storage (swift) before 1.7.0 uses the loads function in the pickle Python module unsafely when storing and loading metadata in memcached, which allows remote attackers to execute arbitrary code via a crafted pickle object.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-502 \u2014 Deserialization of Untrusted Data
The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
Repository: https://github.com/openstack/swift
Files changed:
doc/manpages/proxy-server.conf.5
etc/memcache.conf-sample
etc/proxy-server.conf-sample
swift/common/memcached.py
swift/common/middleware/memcache.py
test/unit/common/middleware/test_memcache.py
test/unit/common/test_memcached.py
Commit message: Do not use pickle for serialization in memcache, but JSON

We don't want to use pickle as it can execute arbitrary code. JSON is
safer. However, note that it supports serialization for only some
specific subset of object types; this should be enough for what we need,
though.

To avoid issues on upgrades (unability to read pickled values, and cache
poisoning for old servers not understanding JSON), we add a
memcache_serialization_support configuration option, with the following
values:

 0 = older, insecure pickle serialization
 1 = json serialization but pickles can still be read (still insecure)
 2 = json serialization only (secure and the default)

To avoid an instant full cache flush, existing installations should
upgrade with 0, then set to 1 and reload, then after some time (24
hours) set to 2 and reload. Support for 0 and 1 will be removed in
future versions.

Part of bug 1006414.

Change-Id: Id7d6d547b103b4f23ebf5be98b88f09ec6027ce4


Vulnerable code:
                 io_timeout=IO_TIMEOUT, tries=TRY_COUNT):
        :param serialize: if True, value is pickled before sending to memcache
        if serialize:
        Gets the object specified by key.  It will also unpickle the object
        before returning if it is pickled in memcache.
                            value = pickle.loads(value)
        :param serialize: if True, value is pickled before sending to memcache
            if serialize:
                            value = pickle.loads(value)
        if not self.memcache_servers:
                try:
                    self.memcache_servers = \
                        memcache_conf.get('memcache', 'memcache_servers')
                except (NoSectionError, NoOptionError):
                    pass
            [s.strip() for s in self.memcache_servers.split(',') if s.strip()])
                    FakeApp(), {'memcache_servers': '1.2.3.4:5'})

Fixed code:
.IP \fBmemcache_serialization_support\fR
This sets how memcache values are serialized and deserialized:
.RE

.PD 0
.RS 10
.IP "0 = older, insecure pickle serialization"
.IP "1 = json serialization but pickles can still be read (still insecure)"
.IP "2 = json serialization only (secure and the default)"
.RE

.RS 10
To avoid an instant full cache flush, existing installations should upgrade with 0, then set to 1 and reload, then after some time (24 hours) set to 2 and reload. In the future, the ability to use pickle serialization will be removed.

If not set in the configuration file, the value for memcache_serialization_support will be read from /etc/swift/memcache.conf if it exists (see memcache.conf-sample). Otherwise, the default value as indicated above will be used.
#
# Sets how memcache values are serialized and deserialized:
# 0 = older, insecure pickle serialization
# 1 = json serialization but pickles can still be read (still insecure)
# 2 = json serialization only (secure and the default)
# To avoid an instant full cache flush, existing installations should
# upgrade with 0, then set to 1 and reload, then after some time (24 hours)
# set to 2 and reload.
# In the future, the ability to use pickle serialization will be removed.
# memcache_serialization_support = 2
#
# Sets how memcache values are serialized and deserialized:
# 0 = older, insecure pickle serialization
# 1 = json serialization but pickles can still be read (still insecure)
# 2 = json serialization only (secure and the default)
# If not set here, the value for memcache_serialization_support will be read
# from /etc/swift/memcache.conf (see memcache.conf-sample).
# To avoid an instant full cache flush, existing installations should
# upgrade with 0, then set to 1 and reload, then after some time (24 hours)
# set to 2 and reload.
# In the future, the ability to use pickle serialization will be removed.
# memcache_serialization_support = 2
try:
    import simplejson as json
except ImportError:
    import json

JSON_FLAG = 2
                 io_timeout=IO_TIMEOUT, tries=TRY_COUNT,
                 allow_pickle=False, allow_unpickle=False):
        self._allow_pickle = allow_pickle
        self._allow_unpickle = allow_unpickle or allow_pickle
        :param serialize: if True, value is serialized with JSON before sending
                          to memcache, or with pickle if configured to use
                          pickle instead of JSON (to avoid cache poisoning)
        if serialize and self._allow_pickle:
        elif serialize:
            value = json.dumps(value)
            flags |= JSON_FLAG
        Gets the object specified by key.  It will also unserialize the object
        before returning if it is serialized in memcache with JSON, or if it
        is pickled and unpickling is allowed.
                            if self._allow_unpickle:
                                value = pickle.loads(value)
                            else:
                                value = None
                        elif int(line[2]) & JSON_FLAG:
                            value = json.loads(value)
        :param serialize: if True, value is serialized with JSON before sending
                          to memcache, or with pickle if configured to use
                          pickle instead of JSON (to avoid cache poisoning)
            if serialize and self._allow_pickle:
            elif serialize:
                value = json.dumps(value)
                flags |= JSON_FLAG
                            if self._allow_unpickle:
                                value = pickle.loads(value)
                            else:
                                value = None
                        elif int(line[2]) & JSON_FLAG:
                            value = json.loads(value)
        serialization_format = conf.get('memcache_serialization_support')

        if not self.memcache_servers or serialization_format is None:
                if not self.memcache_servers:
                    try:
                        self.memcache_servers = \
                            memcache_conf.get('memcache', 'memcache_servers')
                    except (NoSectionError, NoOptionError):
                        pass
                if serialization_format is None:
                    try:
                        serialization_format = \
                            memcache_conf.get('memcache',
                                              'memcache_serialization_support')
                    except (NoSectionError, NoOptionError):
                        pass

        if serialization_format is None:
            serialization_format = 2

            [s.strip() for s in self.memcache_servers.split(',') if s.strip()],
            allow_pickle=(serialization_format == 0),
            allow_unpickle=(serialization_format <= 1))
            elif option == 'memcache_serialization_support':
                return '2'
                    FakeApp(), {'memcache_servers': '1.2.3.4:5',
                                'memcache_serialization_support': '2'})
 # -*- coding: utf8 -*-
        memcache_client.set('some_key', ['simple str', 'utf8 str éà'])
        # As per http://wiki.openstack.org/encoding, we should expect to have unicode
        self.assertEquals(memcache_client.get('some_key'), ['simple str', u'utf8 str éà'])
    def test_serialization(self):
        memcache_client = memcached.MemcacheRing(['1.2.3.4:11211'],
                                                 allow_pickle=True)
        mock = MockMemcached()
        memcache_client._client_cache['1.2.3.4:11211'] = [(mock, mock)] * 2
        memcache_client.set('some_key', [1, 2, 3])
        self.assertEquals(memcache_client.get('some_key'), [1, 2, 3])
        memcache_client._allow_pickle = False
        memcache_client._allow_unpickle = True
        self.assertEquals(memcache_client.get('some_key'), [1, 2, 3])
        memcache_client._allow_unpickle = False
        self.assertEquals(memcache_client.get('some_key'), None)
        memcache_client.set('some_key', [1, 2, 3])
        self.assertEquals(memcache_client.get('some_key'), [1, 2, 3])
        memcache_client._allow_unpickle = True
        self.assertEquals(memcache_client.get('some_key'), [1, 2, 3])
        memcache_client._allow_pickle = True
        self.assertEquals(memcache_client.get('some_key'), [1, 2, 3])