From: Benjamin Peterson Date: Tue, 21 Feb 2012 02:44:56 +0000 (-0500) Subject: merge 2.6 with hash randomization fix X-Git-Tag: v2.7.3rc1~32 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=aee9dfba4a9230f2832dd69d67e92f8e0490a163;p=python merge 2.6 with hash randomization fix --- aee9dfba4a9230f2832dd69d67e92f8e0490a163 diff --cc Doc/library/sys.rst index a52b0d6065,76d84e480e..3873eb8ad1 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@@ -268,25 -256,44 +268,26 @@@ always available The struct sequence *flags* exposes the status of command line flags. The attributes are read only. - +------------------------------+------------------------------------------+ - | attribute | flag | - +==============================+==========================================+ - | :const:`debug` | -d | - +------------------------------+------------------------------------------+ - | :const:`py3k_warning` | -3 | - +------------------------------+------------------------------------------+ - | :const:`division_warning` | -Q | - +------------------------------+------------------------------------------+ - | :const:`division_new` | -Qnew | - +------------------------------+------------------------------------------+ - | :const:`inspect` | -i | - +------------------------------+------------------------------------------+ - | :const:`interactive` | -i | - +------------------------------+------------------------------------------+ - | :const:`optimize` | -O or -OO | - +------------------------------+------------------------------------------+ - | :const:`dont_write_bytecode` | -B | - +------------------------------+------------------------------------------+ - | :const:`no_user_site` | -s | - +------------------------------+------------------------------------------+ - | :const:`no_site` | -S | - +------------------------------+------------------------------------------+ - | :const:`ignore_environment` | -E | - +------------------------------+------------------------------------------+ - | :const:`tabcheck` | -t or -tt | - +------------------------------+------------------------------------------+ - | :const:`verbose` | -v | - +------------------------------+------------------------------------------+ - | :const:`unicode` | -U | - +------------------------------+------------------------------------------+ - | :const:`bytes_warning` | -b | - +------------------------------+------------------------------------------+ - +------------------------------+------------------------------------------+ - | :const:`hash_randomization` | -R | - | | | - | | .. versionadded:: 2.6.8 | - +------------------------------+------------------------------------------+ + ============================= =================================== + attribute flag + ============================= =================================== + :const:`debug` :option:`-d` + :const:`py3k_warning` :option:`-3` + :const:`division_warning` :option:`-Q` + :const:`division_new` :option:`-Qnew <-Q>` + :const:`inspect` :option:`-i` + :const:`interactive` :option:`-i` + :const:`optimize` :option:`-O` or :option:`-OO` + :const:`dont_write_bytecode` :option:`-B` + :const:`no_user_site` :option:`-s` + :const:`no_site` :option:`-S` + :const:`ignore_environment` :option:`-E` + :const:`tabcheck` :option:`-t` or :option:`-tt <-t>` + :const:`verbose` :option:`-v` + :const:`unicode` :option:`-U` + :const:`bytes_warning` :option:`-b` ++ :const:`hash_randomization` :option:`-R` + ============================= =================================== .. versionadded:: 2.6 diff --cc Doc/using/cmdline.rst index 29d249f00f,38d724f472..0d2924de75 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@@ -253,10 -239,32 +253,33 @@@ Miscellaneous option :pep:`238` -- Changing the division operator + .. cmdoption:: -R + + Turn on hash randomization, so that the :meth:`__hash__` values of str, + bytes and datetime objects are "salted" with an unpredictable random value. + Although they remain constant within an individual Python process, they are + not predictable between repeated invocations of Python. + + This is intended to provide protection against a denial-of-service caused by + carefully-chosen inputs that exploit the worst case performance of a dict + insertion, O(n^2) complexity. See + http://www.ocert.org/advisories/ocert-2011-003.html for details. + + Changing hash values affects the order in which keys are retrieved from a + dict. Although Python has never made guarantees about this ordering (and it + typically varies between 32-bit and 64-bit builds), enough real-world code + implicitly relies on this non-guaranteed behavior that the randomization is + disabled by default. + + See also :envvar:`PYTHONHASHSEED`. + + .. versionadded:: 2.6.8 + + .. cmdoption:: -s - Don't add user site directory to sys.path + Don't add the :data:`user site-packages directory ` to + :data:`sys.path`. .. versionadded:: 2.6 diff --cc Lib/test/test_os.py index eec1621301,0561499ed7..d52b78462e --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@@ -7,12 -6,9 +7,13 @@@ import errn import unittest import warnings import sys +import signal import subprocess +import time + from test import test_support +import mmap +import uuid warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, __name__) warnings.filterwarnings("ignore", "tmpnam", RuntimeWarning, __name__) @@@ -533,14 -507,40 +534,47 @@@ class URandomTests (unittest.TestCase) self.assertEqual(len(os.urandom(100)), 100) self.assertEqual(len(os.urandom(1000)), 1000) # see http://bugs.python.org/issue3708 + self.assertRaises(TypeError, os.urandom, 0.9) + self.assertRaises(TypeError, os.urandom, 1.1) + self.assertRaises(TypeError, os.urandom, 2.0) - except NotImplementedError: - pass + self.assertEqual(len(os.urandom(0.9)), 0) + self.assertEqual(len(os.urandom(1.1)), 1) + self.assertEqual(len(os.urandom(2.0)), 2) + + def test_urandom_length(self): + self.assertEqual(len(os.urandom(0)), 0) + self.assertEqual(len(os.urandom(1)), 1) + self.assertEqual(len(os.urandom(10)), 10) + self.assertEqual(len(os.urandom(100)), 100) + self.assertEqual(len(os.urandom(1000)), 1000) + + def test_urandom_value(self): + data1 = os.urandom(16) + data2 = os.urandom(16) + self.assertNotEqual(data1, data2) + + def get_urandom_subprocess(self, count): + code = '\n'.join(( + 'import os, sys', + 'data = os.urandom(%s)' % count, + 'sys.stdout.write(data)', + 'sys.stdout.flush()')) + cmd_line = [sys.executable, '-c', code] + p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, err = p.communicate() + out = test_support.strip_python_stderr(out) + self.assertEqual(len(out), count) + return out + + def test_urandom_subprocess(self): + data1 = self.get_urandom_subprocess(16) + data2 = self.get_urandom_subprocess(16) + self.assertNotEqual(data1, data2) ++>>>>>>> other + + def test_execvpe_with_bad_arglist(self): + self.assertRaises(ValueError, os.execvpe, 'notepad', [], None) class Win32ErrorTests(unittest.TestCase): def test_rename(self): diff --cc Lib/test/test_support.py index a03cf1473b,b572f9abe5..b0a2048c8d --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@@ -34,10 -24,7 +34,10 @@@ __all__ = ["Error", "TestFailed", "Reso "captured_stdout", "TransientResource", "transient_internet", "run_with_locale", "set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner", "run_unittest", "run_doctest", "threading_setup", - "threading_cleanup", "reap_children", "strip_python_stderr"] + "threading_cleanup", "reap_children", "cpython_only", + "check_impl_detail", "get_attribute", "py3k_bytes", - "import_fresh_module"] - ++ "import_fresh_module", "threading_cleanup", "reap_children", ++ "strip_python_stderr"] class Error(Exception): """Base class for regression test exceptions.""" diff --cc Lib/test/test_sys.py index 6e37ac4487,e82569ac02..12d4d31194 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@@ -438,11 -384,11 +438,11 @@@ class SysModuleTest(unittest.TestCase) attrs = ("debug", "py3k_warning", "division_warning", "division_new", "inspect", "interactive", "optimize", "dont_write_bytecode", "no_site", "ignore_environment", "tabcheck", "verbose", - "unicode", "bytes_warning") + "unicode", "bytes_warning", "hash_randomization") for attr in attrs: - self.assert_(hasattr(sys.flags, attr), attr) + self.assertTrue(hasattr(sys.flags, attr), attr) self.assertEqual(type(getattr(sys.flags, attr)), int, attr) - self.assert_(repr(sys.flags)) + self.assertTrue(repr(sys.flags)) def test_clear_type_cache(self): sys._clear_type_cache() diff --cc Makefile.pre.in index 340da24762,ce4fbdc460..e2237a96fa --- a/Makefile.pre.in +++ b/Makefile.pre.in @@@ -739,12 -709,11 +740,12 @@@ buildbottest: all platfor -@if which pybuildbot.identify >/dev/null 2>&1; then \ pybuildbot.identify "CC='$(CC)'" "CXX='$(CXX)'"; \ fi - $(TESTPYTHON) $(TESTPROG) -uall -rwW $(TESTOPTS) - $(TESTPYTHON) -R $(TESTPROG) -uall -rw $(TESTOPTS) ++ $(TESTPYTHON) -R $(TESTPROG) -uall -rwW $(TESTOPTS) -QUICKTESTOPTS= $(TESTOPTS) -x test_thread test_signal test_strftime \ - test_unicodedata test_re test_sre test_select test_poll \ - test_linuxaudiodev test_struct test_sunaudiodev test_zlib +QUICKTESTOPTS= $(TESTOPTS) -x test_subprocess test_io test_lib2to3 \ + test_multibytecodec test_urllib2_localnet test_itertools \ + test_multiprocessing test_mailbox test_socket test_poll \ + test_select test_zipfile quicktest: all platform -find $(srcdir)/Lib -name '*.py[co]' -print | xargs rm -f -$(TESTPYTHON) $(TESTPROG) $(QUICKTESTOPTS) diff --cc Misc/NEWS index 22adf56c0b,2b2f94cacf..1c049876ab --- a/Misc/NEWS +++ b/Misc/NEWS @@@ -9,86 -10,75 +9,91 @@@ What's New in Python 2.7.3 Core and Builtins ----------------- +- Issue #13020: Fix a reference leak when allocating a structsequence object + fails. Patch by Suman Saha. + + - Issue #13703: oCERT-2011-003: add -R command-line option and PYTHONHASHSEED + environment variable, to provide an opt-in way to protect against denial of + service attacks due to hash collisions within the dict and set types. Patch + by David Malcolm, based on work by Victor Stinner. + -Library -------- - -- Issue #14001: CVE-2012-0845: xmlrpc: Fix an endless loop in - SimpleXMLRPCServer upon malformed POST request. - -- Issue #13885: CVE-2011-3389: the _ssl module would always disable the CBC - IV attack countermeasure. - - -What's New in Python 2.6.7? -=========================== - -*Release date: 2011-06-03* +- Issue #11235: Fix OverflowError when trying to import a source file whose + modification time doesn't fit in a 32-bit timestamp. -*NOTE: Python 2.6 is in security-fix-only mode. No non-security bug fixes are - allowed. Python 2.6.7 and beyond will be source only releases.* +- Issue #11638: Unicode strings in 'name' and 'version' no longer cause + UnicodeDecodeErrors. -* No changes since 2.6.7rc2. +- Fix the fix for issue #12149: it was incorrect, although it had the side + effect of appearing to resolve the issue. Thanks to Mark Shannon for + noticing. +- Issue #13546: Fixed an overflow issue that could crash the intepreter when + calling sys.setrecursionlimit((1<<31)-1). -What's New in Python 2.6.7 rc 2? -================================ +- Issue #13333: The UTF-7 decoder now accepts lone surrogates (the encoder + already accepts them). -*Release date: 2011-05-20* +- Issue #10519: Avoid unnecessary recursive function calls in + setobject.c. -*NOTE: Python 2.6 is in security-fix-only mode. No non-security bug fixes are - allowed. Python 2.6.7 and beyond will be source only releases.* +- Issue #13268: Fix the assert statement when a tuple is passed as the message. +- Issue #13018: Fix reference leaks in error paths in dictobject.c. + Patch by Suman Saha. -Library -------- +- Issue #12604: VTRACE macro expanded to no-op in _sre.c to avoid compiler + warnings. Patch by Josh Triplett and Petri Lehtinen. -- Issue #11662: Make urllib and urllib2 ignore redirections if the - scheme is not HTTP, HTTPS or FTP (CVE-2011-1521). +- Issue #7833: Extension modules built using distutils on Windows will no + longer include a "manifest" to prevent them failing at import time in some + embedded situations. -- Issue #11442: Add a charset parameter to the Content-type in SimpleHTTPServer - to avoid XSS attacks. +- Issue #13186: Fix __delitem__ on old-style instances when invoked through + PySequence_DelItem. +- Issue #13156: Revert the patch for issue #10517 (reset TLS upon fork()), + which was only relevant for the native pthread TLS implementation. -What's New in Python 2.6.7 rc 1? -================================ +- Issue #7732: Fix a crash on importing a module if a directory has the same + name than a Python module (e.g. "__init__.py"): don't close the file twice. -*Release date: 2011-05-06* +- Issue #12973: Fix overflow checks that invoked undefined behaviour in + int.__pow__. These overflow checks were causing int.__pow__ to produce + incorrect results with recent versions of Clang, as a result of the + compiler optimizing the check away. Also fix similar overflow checks + in list_repeat (listobject.c) and islice_next (itertoolsmodule.c). These + bugs caused test failures with recent versions of Clang. -Library -------- +- Issue #12266: Fix str.capitalize() to correctly uppercase/lowercase + titlecased and cased non-letter characters. -- Issue #9129: smtpd.py is vulnerable to DoS attacks deriving from missing - error handling when accepting a new connection. +- Issues #12610 and #12609: Verify that user generated AST has correct string + and identifier types before compiling. +- Issue #11627: Fix segfault when __new__ on a exception returns a + non-exception class. -What's New in Python 2.6.6? -=========================== +- Issue #12149: Update the method cache after a type's dictionnary gets + cleared by the garbage collector. This fixes a segfault when an instance + and its type get caught in a reference cycle, and the instance's + deallocator calls one of the methods on the type (e.g. when subclassing + IOBase). Diagnosis and patch by Davide Rizzo. -*Release date: 2010-08-24* +- Issue #12501: Remove Py3k warning for callable. callable() is supported + again in Python 3.2. -* No changes since 2.6.6rc2. +- Issue #9611, #9015: FileIO.read(), FileIO.readinto(), FileIO.write() and + os.write() clamp the length to INT_MAX on Windows. +- Issue #1195: my_fgets() now always clears errors before calling fgets(). Fix + the following case: sys.stdin.read() stopped with CTRL+d (end of file), + raw_input() interrupted by CTRL+c. -What's New in Python 2.6.6 rc 2? -================================ +- Issue #10860: httplib now correctly handles an empty port after port + delimiter in URLs. -*Release date: 2010-08-16* +- dict_proxy objects now display their contents rather than just the class + name. Library ------- diff --cc Misc/python.man index 93087876c0,0a3eca476f..91787cdd0f --- a/Misc/python.man +++ b/Misc/python.man @@@ -31,9 -31,12 +31,12 @@@ python \- an interpreted, interactive, .B \-O ] [ -.B \-O0 +.B \-OO ] [ + .B \-R + ] + [ .B -Q .I argument ] @@@ -148,9 -151,21 +151,21 @@@ compiled (bytecode) files fro .I .pyc to \fI.pyo\fP. Given twice, causes docstrings to be discarded. .TP -.B \-O0 +.B \-OO Discard docstrings in addition to the \fB-O\fP optimizations. .TP + .B \-R + Turn on "hash randomization", so that the hash() values of str, bytes and + datetime objects are "salted" with an unpredictable pseudo-random value. + Although they remain constant within an individual Python process, they are + not predictable between repeated invocations of Python. + .IP + This is intended to provide protection against a denial of service + caused by carefully-chosen inputs that exploit the worst case performance + of a dict insertion, O(n^2) complexity. See + http://www.ocert.org/advisories/ocert-2011-003.html + for details. + .TP .BI "\-Q " argument Division control; see PEP 238. The argument must be one of "old" (the default, int/int and long/long return an int or long), "new" (new @@@ -420,9 -426,20 +435,23 @@@ the \fB\-u\fP option If this is set to a non-empty string it is equivalent to specifying the \fB\-v\fP option. If set to an integer, it is equivalent to specifying \fB\-v\fP multiple times. +.IP PYTHONWARNINGS +If this is set to a comma-separated string it is equivalent to +specifying the \fB\-W\fP option for each separate value. + .IP PYTHONHASHSEED + If this variable is set to "random", the effect is the same as specifying + the \fB-R\fP option: a random value is used to seed the hashes of str, + bytes and datetime objects. + + If PYTHONHASHSEED is set to an integer value, it is used as a fixed seed for + generating the hash() of the types covered by the hash randomization. Its + purpose is to allow repeatable hashing, such as for selftests for the + interpreter itself, or to allow a cluster of python processes to share hash + values. + + The integer must be a decimal number in the range [0,4294967295]. Specifying + the value 0 will lead to the same hash values as when hash randomization is + disabled. .SH AUTHOR The Python Software Foundation: http://www.python.org/psf .SH INTERNET RESOURCES diff --cc Modules/posixmodule.c index d3b14c17fc,eedd2ea2e9..1bd5c1ac68 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@@ -8648,84 -8400,7 +8567,83 @@@ posix_urandom(PyObject *self, PyObject } return result; } - #endif +#ifdef HAVE_SETRESUID +PyDoc_STRVAR(posix_setresuid__doc__, +"setresuid(ruid, euid, suid)\n\n\ +Set the current process's real, effective, and saved user ids."); + +static PyObject* +posix_setresuid (PyObject *self, PyObject *args) +{ + /* We assume uid_t is no larger than a long. */ + long ruid, euid, suid; + if (!PyArg_ParseTuple(args, "lll", &ruid, &euid, &suid)) + return NULL; + if (setresuid(ruid, euid, suid) < 0) + return posix_error(); + Py_RETURN_NONE; +} +#endif + +#ifdef HAVE_SETRESGID +PyDoc_STRVAR(posix_setresgid__doc__, +"setresgid(rgid, egid, sgid)\n\n\ +Set the current process's real, effective, and saved group ids."); + +static PyObject* +posix_setresgid (PyObject *self, PyObject *args) +{ + /* We assume uid_t is no larger than a long. */ + long rgid, egid, sgid; + if (!PyArg_ParseTuple(args, "lll", &rgid, &egid, &sgid)) + return NULL; + if (setresgid(rgid, egid, sgid) < 0) + return posix_error(); + Py_RETURN_NONE; +} +#endif + +#ifdef HAVE_GETRESUID +PyDoc_STRVAR(posix_getresuid__doc__, +"getresuid() -> (ruid, euid, suid)\n\n\ +Get tuple of the current process's real, effective, and saved user ids."); + +static PyObject* +posix_getresuid (PyObject *self, PyObject *noargs) +{ + uid_t ruid, euid, suid; + long l_ruid, l_euid, l_suid; + if (getresuid(&ruid, &euid, &suid) < 0) + return posix_error(); + /* Force the values into long's as we don't know the size of uid_t. */ + l_ruid = ruid; + l_euid = euid; + l_suid = suid; + return Py_BuildValue("(lll)", l_ruid, l_euid, l_suid); +} +#endif + +#ifdef HAVE_GETRESGID +PyDoc_STRVAR(posix_getresgid__doc__, +"getresgid() -> (rgid, egid, sgid)\n\n\ +Get tuple of the current process's real, effective, and saved group ids."); + +static PyObject* +posix_getresgid (PyObject *self, PyObject *noargs) +{ + uid_t rgid, egid, sgid; + long l_rgid, l_egid, l_sgid; + if (getresgid(&rgid, &egid, &sgid) < 0) + return posix_error(); + /* Force the values into long's as we don't know the size of uid_t. */ + l_rgid = rgid; + l_egid = egid; + l_sgid = sgid; + return Py_BuildValue("(lll)", l_rgid, l_egid, l_sgid); +} +#endif + static PyMethodDef posix_methods[] = { {"access", posix_access, METH_VARARGS, posix_access__doc__}, #ifdef HAVE_TTYNAME @@@ -9034,26 -8704,8 +8952,20 @@@ #endif #ifdef HAVE_GETLOADAVG {"getloadavg", posix_getloadavg, METH_NOARGS, posix_getloadavg__doc__}, +#endif - #ifdef MS_WINDOWS - {"urandom", win32_urandom, METH_VARARGS, win32_urandom__doc__}, - #endif - #ifdef __VMS - {"urandom", vms_urandom, METH_VARARGS, vms_urandom__doc__}, - #endif +#ifdef HAVE_SETRESUID + {"setresuid", posix_setresuid, METH_VARARGS, posix_setresuid__doc__}, +#endif +#ifdef HAVE_SETRESGID + {"setresgid", posix_setresgid, METH_VARARGS, posix_setresgid__doc__}, +#endif +#ifdef HAVE_GETRESUID + {"getresuid", posix_getresuid, METH_NOARGS, posix_getresuid__doc__}, +#endif +#ifdef HAVE_GETRESGID + {"getresgid", posix_getresgid, METH_NOARGS, posix_getresgid__doc__}, #endif - + {"urandom", posix_urandom, METH_VARARGS, posix_urandom__doc__}, {NULL, NULL} /* Sentinel */ }; diff --cc PCbuild/pythoncore.vcproj index 03a9a65f39,d09e0110f4..5a5eed4949 --- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@@ -1,1873 -1,1821 +1,3697 @@@ ++<<<<<<< local + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++>>>>>>> other