diff --git a/swift/account/server.py b/swift/account/server.py index 0b80b3d5e2..f7a16e6dbd 100644 --- a/swift/account/server.py +++ b/swift/account/server.py @@ -32,7 +32,8 @@ import simplejson import swift.common.db from swift.common.db import AccountBroker from swift.common.utils import get_logger, get_param, hash_path, public, \ - normalize_timestamp, split_path, storage_directory, TRUE_VALUES + normalize_timestamp, split_path, storage_directory, TRUE_VALUES, \ + validate_device_partition from swift.common.constraints import ACCOUNT_LISTING_LIMIT, \ check_mount, check_float, check_utf8 from swift.common.db_replicator import ReplicatorRpc @@ -69,6 +70,7 @@ class AccountController(object): start_time = time.time() try: drive, part, account = split_path(unquote(req.path), 3) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('DELETE.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -96,6 +98,7 @@ class AccountController(object): try: drive, part, account, container = split_path(unquote(req.path), 3, 4) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('PUT.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -164,6 +167,7 @@ class AccountController(object): try: drive, part, account, container = split_path(unquote(req.path), 3, 4) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('HEAD.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -201,6 +205,7 @@ class AccountController(object): start_time = time.time() try: drive, part, account = split_path(unquote(req.path), 3) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('GET.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -305,11 +310,12 @@ class AccountController(object): start_time = time.time() try: post_args = split_path(unquote(req.path), 3) + drive, partition, hash = post_args + validate_device_partition(drive, partition) except ValueError, err: self.logger.increment('REPLICATE.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', request=req) - drive, partition, hash = post_args if self.mount_check and not check_mount(self.root, drive): self.logger.increment('REPLICATE.errors') return HTTPInsufficientStorage(drive=drive, request=req) @@ -329,6 +335,7 @@ class AccountController(object): start_time = time.time() try: drive, part, account = split_path(unquote(req.path), 3) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('POST.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', diff --git a/swift/common/utils.py b/swift/common/utils.py index a353a5c122..cfc0a1d46b 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -263,6 +263,28 @@ def split_path(path, minsegs=1, maxsegs=None, rest_with_last=False): return segs +def validate_device_partition(device, partition): + """ + Validate that a device and a partition are valid and won't lead to + directory traversal when used. + + :param device: device to validate + :param partition: partition to validate + :raises: ValueError if given an invalid device or partition + """ + invalid_device = False + invalid_partition = False + if not device or '/' in device or device in ['.', '..']: + invalid_device = True + if not partition or '/' in partition or partition in ['.', '..']: + invalid_partition = True + + if invalid_device: + raise ValueError('Invalid device: %s' % quote(device or '')) + elif invalid_partition: + raise ValueError('Invalid partition: %s' % quote(partition or '')) + + class NullLogger(): """A no-op logger for eventlet wsgi.""" diff --git a/swift/container/server.py b/swift/container/server.py index 17098e2241..0e389c1fc7 100644 --- a/swift/container/server.py +++ b/swift/container/server.py @@ -33,7 +33,7 @@ import swift.common.db from swift.common.db import ContainerBroker from swift.common.utils import get_logger, get_param, hash_path, public, \ normalize_timestamp, storage_directory, split_path, validate_sync_to, \ - TRUE_VALUES + TRUE_VALUES, validate_device_partition from swift.common.constraints import CONTAINER_LISTING_LIMIT, \ check_mount, check_float, check_utf8 from swift.common.bufferedhttp import http_connect @@ -145,6 +145,7 @@ class ContainerController(object): try: drive, part, account, container, obj = split_path( unquote(req.path), 4, 5, True) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('DELETE.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -195,6 +196,7 @@ class ContainerController(object): try: drive, part, account, container, obj = split_path( unquote(req.path), 4, 5, True) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('PUT.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -264,6 +266,7 @@ class ContainerController(object): try: drive, part, account, container, obj = split_path( unquote(req.path), 4, 5, True) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('HEAD.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -298,6 +301,7 @@ class ContainerController(object): try: drive, part, account, container, obj = split_path( unquote(req.path), 4, 5, True) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('GET.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', @@ -421,11 +425,12 @@ class ContainerController(object): start_time = time.time() try: post_args = split_path(unquote(req.path), 3) + drive, partition, hash = post_args + validate_device_partition(drive, partition) except ValueError, err: self.logger.increment('REPLICATE.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', request=req) - drive, partition, hash = post_args if self.mount_check and not check_mount(self.root, drive): self.logger.increment('REPLICATE.errors') return HTTPInsufficientStorage(drive=drive, request=req) @@ -445,6 +450,7 @@ class ContainerController(object): start_time = time.time() try: drive, part, account, container = split_path(unquote(req.path), 4) + validate_device_partition(drive, part) except ValueError, err: self.logger.increment('POST.errors') return HTTPBadRequest(body=str(err), content_type='text/plain', diff --git a/swift/obj/server.py b/swift/obj/server.py index 6627309a41..66681d4268 100644 --- a/swift/obj/server.py +++ b/swift/obj/server.py @@ -38,7 +38,7 @@ from eventlet import sleep, Timeout, tpool from swift.common.utils import mkdirs, normalize_timestamp, public, \ storage_directory, hash_path, renamer, fallocate, \ split_path, drop_buffer_cache, get_logger, write_pickle, \ - TRUE_VALUES + TRUE_VALUES, validate_device_partition from swift.common.bufferedhttp import http_connect from swift.common.constraints import check_object_creation, check_mount, \ check_float, check_utf8 @@ -494,6 +494,7 @@ class ObjectController(object): try: device, partition, account, container, obj = \ split_path(unquote(request.path), 5, 5, True) + validate_device_partition(device, partition) except ValueError, err: self.logger.increment('POST.errors') return HTTPBadRequest(body=str(err), request=request, @@ -554,6 +555,7 @@ class ObjectController(object): try: device, partition, account, container, obj = \ split_path(unquote(request.path), 5, 5, True) + validate_device_partition(device, partition) except ValueError, err: self.logger.increment('PUT.errors') return HTTPBadRequest(body=str(err), request=request, @@ -653,6 +655,7 @@ class ObjectController(object): try: device, partition, account, container, obj = \ split_path(unquote(request.path), 5, 5, True) + validate_device_partition(device, partition) except ValueError, err: self.logger.increment('GET.errors') return HTTPBadRequest(body=str(err), request=request, @@ -743,6 +746,7 @@ class ObjectController(object): try: device, partition, account, container, obj = \ split_path(unquote(request.path), 5, 5, True) + validate_device_partition(device, partition) except ValueError, err: self.logger.increment('HEAD.errors') resp = HTTPBadRequest(request=request) @@ -789,6 +793,7 @@ class ObjectController(object): try: device, partition, account, container, obj = \ split_path(unquote(request.path), 5, 5, True) + validate_device_partition(device, partition) except ValueError, e: self.logger.increment('DELETE.errors') return HTTPBadRequest(body=str(e), request=request, @@ -843,6 +848,7 @@ class ObjectController(object): try: device, partition, suffix = split_path( unquote(request.path), 2, 3, True) + validate_device_partition(device, partition) except ValueError, e: self.logger.increment('REPLICATE.errors') return HTTPBadRequest(body=str(e), request=request, diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index 4891351526..4a5b5a7119 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -192,6 +192,27 @@ class TestUtils(unittest.TestCase): except ValueError, err: self.assertEquals(str(err), 'Invalid path: o%0An%20e') + def test_validate_device_partition(self): + """ Test swift.common.utils.validate_device_partition """ + utils.validate_device_partition('foo', 'bar') + self.assertRaises(ValueError, utils.validate_device_partition, '', '') + self.assertRaises(ValueError, utils.validate_device_partition, '', 'foo') + self.assertRaises(ValueError, utils.validate_device_partition, 'foo', '') + self.assertRaises(ValueError, utils.validate_device_partition, 'foo/bar', 'foo') + self.assertRaises(ValueError, utils.validate_device_partition, 'foo', 'foo/bar') + self.assertRaises(ValueError, utils.validate_device_partition, '.', 'foo') + self.assertRaises(ValueError, utils.validate_device_partition, '..', 'foo') + self.assertRaises(ValueError, utils.validate_device_partition, 'foo', '.') + self.assertRaises(ValueError, utils.validate_device_partition, 'foo', '..') + try: + utils.validate_device_partition,('o\nn e', 'foo') + except ValueError, err: + self.assertEquals(str(err), 'Invalid device: o%0An%20e') + try: + utils.validate_device_partition,('foo', 'o\nn e') + except ValueError, err: + self.assertEquals(str(err), 'Invalid partition: o%0An%20e') + def test_NullLogger(self): """ Test swift.common.utils.NullLogger """ sio = StringIO()