Hi,

I have prepared security updates for radicale in Wheezy and Jessie. This
is Debian bug #809920 [1]. I have tested both patches and they were
reviewed by upstream. The debdiffs are attached to this e-mail.

Regards,

Markus


[1] https://bugs.debian.org/809920
diff -Nru radicale-0.9/debian/changelog radicale-0.9/debian/changelog
--- radicale-0.9/debian/changelog       2014-08-25 02:45:58.000000000 +0200
+++ radicale-0.9/debian/changelog       2016-01-28 17:50:28.000000000 +0100
@@ -1,3 +1,13 @@
+radicale (0.9-1+deb8u1) jessie-security; urgency=high
+
+  * Non-maintainer upload by the Security Team.
+  * CVE-2015-8748 and CVE-2015-8747:
+    Fix insecure path handling by sanitizing system paths and always
+    making them absolute. Fix multifilesystem backend allowed access
+    to arbitrary files on all platforms. (Closes: #809920)
+
+ -- Markus Koschany <a...@debian.org>  Thu, 28 Jan 2016 01:02:54 +0100
+
 radicale (0.9-1) unstable; urgency=medium
 
   [ upstream ]
diff -Nru radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 
radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch
--- radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch   
1970-01-01 01:00:00.000000000 +0100
+++ radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch   
2016-01-28 17:50:28.000000000 +0100
@@ -0,0 +1,415 @@
+From: Markus Koschany <a...@debian.org>
+Date: Thu, 28 Jan 2016 01:00:41 +0100
+Subject: CVE-2015-8748 and CVE-2015-8747
+
+Fix insecure path handling by sanitizing system paths and always
+making them absolute. Fix multifilesystem backend allowed access
+to arbitrary files on all platforms.
+
+The patch was kindly reviewed by upstream.
+
+Origin: https://github.com/Kozea/Radicale/pull/343/commits
+Debian-Bug: https://bugs.debian.org/809920
+---
+ radicale/__init__.py                | 26 ++++++------
+ radicale/ical.py                    | 11 ++---
+ radicale/pathutils.py               | 84 +++++++++++++++++++++++++++++++++++++
+ radicale/storage/filesystem.py      | 41 +++++++++++-------
+ radicale/storage/multifilesystem.py | 47 ++++++++++++++-------
+ 5 files changed, 160 insertions(+), 49 deletions(-)
+ create mode 100644 radicale/pathutils.py
+
+diff --git a/radicale/__init__.py b/radicale/__init__.py
+index 2824efd..694a447 100644
+--- a/radicale/__init__.py
++++ b/radicale/__init__.py
+@@ -32,7 +32,6 @@ import os
+ import sys
+ import pprint
+ import base64
+-import posixpath
+ import socket
+ import ssl
+ import wsgiref.simple_server
+@@ -47,7 +46,7 @@ except ImportError:
+     from urlparse import urlparse
+ # pylint: enable=F0401,E0611
+ 
+-from . import auth, config, ical, log, rights, storage, xmlutils
++from . import auth, config, ical, log, pathutils, rights, storage, xmlutils
+ 
+ 
+ VERSION = "0.9"
+@@ -174,12 +173,9 @@ class Application(object):
+ 
+     @staticmethod
+     def sanitize_uri(uri):
+-        """Unquote and remove /../ to prevent access to other data."""
++        """Unquote and make absolute to prevent access to other data."""
+         uri = unquote(uri)
+-        trailing_slash = "/" if uri.endswith("/") else ""
+-        uri = posixpath.normpath(uri)
+-        trailing_slash = "" if uri == "/" else trailing_slash
+-        return uri + trailing_slash
++        return pathutils.sanitize_path(uri)
+ 
+     def collect_allowed_items(self, items, user):
+         """Get items from request that user is allowed to access."""
+@@ -248,18 +244,21 @@ class Application(object):
+ 
+         base_prefix = config.get("server", "base_prefix")
+         if environ["PATH_INFO"].startswith(base_prefix):
+-            # Sanitize request URI
+-            environ["PATH_INFO"] = self.sanitize_uri(
+-                "/%s" % environ["PATH_INFO"][len(base_prefix):])
+-            log.LOGGER.debug("Sanitized path: %s", environ["PATH_INFO"])
++            environ["PATH_INFO"] = environ["PATH_INFO"][len(base_prefix):]
+         elif config.get("server", "can_skip_base_prefix"):
+             log.LOGGER.debug(
+-                "Skipped already sanitized path: %s", environ["PATH_INFO"])
++                "Prefix already stripped from path: %s", environ["PATH_INFO"])
+         else:
+             # Request path not starting with base_prefix, not allowed
+             log.LOGGER.debug(
+                 "Path not starting with prefix: %s", environ["PATH_INFO"])
+-            environ["PATH_INFO"] = None
++            status, headers, _ = NOT_ALLOWED
++            start_response(status, list(headers.items()))
++            return []
++
++        # Sanitize request URI
++        environ["PATH_INFO"] = self.sanitize_uri(environ["PATH_INFO"])
++        log.LOGGER.debug("Sanitized path: %s", environ["PATH_INFO"])
+ 
+         # Get content
+         content_length = int(environ.get("CONTENT_LENGTH") or 0)
+@@ -270,6 +269,7 @@ class Application(object):
+         else:
+             content = None
+ 
++
+         path = environ["PATH_INFO"]
+ 
+         # Get function corresponding to method
+diff --git a/radicale/ical.py b/radicale/ical.py
+index a9db439..40e8793 100644
+--- a/radicale/ical.py
++++ b/radicale/ical.py
+@@ -26,11 +26,11 @@ Define the main classes of a collection as seen from the 
server.
+ """
+ 
+ import os
+-import posixpath
+ import hashlib
+ from uuid import uuid4
+ from random import randint
+ from contextlib import contextmanager
++from . import pathutils
+ 
+ 
+ def serialize(tag, headers=(), items=()):
+@@ -188,8 +188,9 @@ class Collection(object):
+ 
+         """
+         self.encoding = "utf-8"
+-        split_path = path.split("/")
+-        self.path = path if path != "." else ""
++        # path should already be sanitized
++        self.path = pathutils.sanitize_path(path).strip("/")
++        split_path = self.path.split("/")
+         if principal and split_path and self.is_node(self.path):
+             # Already existing principal collection
+             self.owner = split_path[0]
+@@ -219,8 +220,8 @@ class Collection(object):
+         if path is None:
+             return []
+ 
+-        # First do normpath and then strip, to prevent access to FOLDER/../
+-        sane_path = posixpath.normpath(path.replace(os.sep, "/")).strip("/")
++        # path should already be sanitized
++        sane_path = pathutils.sanitize_path(path).strip("/")
+         attributes = sane_path.split("/")
+         if not attributes:
+             return []
+diff --git a/radicale/pathutils.py b/radicale/pathutils.py
+new file mode 100644
+index 0000000..2aa13af
+--- /dev/null
++++ b/radicale/pathutils.py
+@@ -0,0 +1,84 @@
++# -*- coding: utf-8 -*-
++#
++# This file is part of Radicale Server - Calendar Server
++#
++# This library is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# This library is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
++
++"""
++Helper functions for working with paths
++
++"""
++
++import os
++import posixpath
++
++from . import log
++
++
++def sanitize_path(path):
++    """Make absolute (with leading slash) to prevent access to other data.
++       Preserves an potential trailing slash."""
++    trailing_slash = "/" if path.endswith("/") else ""
++    path = posixpath.normpath(path)
++    new_path = "/"
++    for part in path.split("/"):
++        if not part or part in (".", ".."):
++            continue
++        new_path = posixpath.join(new_path, part)
++    trailing_slash = "" if new_path.endswith("/") else trailing_slash
++    return new_path + trailing_slash
++
++
++def is_safe_path_component(path):
++    """Checks if path is a single component of a path and is safe to join"""
++    if not path:
++        return False
++    head, _ = posixpath.split(path)
++    if head:
++        return False
++    if path in (".", ".."):
++        return False
++    return True
++
++
++def is_safe_filesystem_path_component(path):
++    """Checks if path is a single component of a local filesystem path
++       and is safe to join"""
++    if not path:
++        return False
++    drive, _ = os.path.splitdrive(path)
++    if drive:
++        return False
++    head, _ = os.path.split(path)
++    if head:
++        return False
++    if path in (os.curdir, os.pardir):
++        return False
++    return True
++
++
++def path_to_filesystem(path, base_folder):
++    """Converts path to a local filesystem path relative to base_folder
++        in a secure manner or raises ValueError."""
++    sane_path = sanitize_path(path).strip("/")
++    safe_path = base_folder
++    if not sane_path:
++        return safe_path
++    for part in sane_path.split("/"):
++        if not is_safe_filesystem_path_component(part):
++            log.LOGGER.debug("Can't translate path safely to filesystem: %s",
++                             path)
++            raise ValueError("Unsafe path")
++        safe_path = os.path.join(safe_path, part)
++    return safe_path
+\ No newline at end of file
+diff --git a/radicale/storage/filesystem.py b/radicale/storage/filesystem.py
+index 238f6ec..2363547 100644
+--- a/radicale/storage/filesystem.py
++++ b/radicale/storage/filesystem.py
+@@ -28,7 +28,8 @@ import json
+ import time
+ import sys
+ from contextlib import contextmanager
+-from .. import config, ical
++
++from .. import config, ical, log, pathutils
+ 
+ 
+ FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder"))
+@@ -62,59 +63,67 @@ def open(path, mode="r"):
+ class Collection(ical.Collection):
+     """Collection stored in a flat ical file."""
+     @property
+-    def _path(self):
++    def _filesystem_path(self):
+         """Absolute path of the file at local ``path``."""
+-        return os.path.join(FOLDER, self.path.replace("/", os.sep))
++        return pathutils.path_to_filesystem(self.path, FOLDER)
+ 
+     @property
+     def _props_path(self):
+         """Absolute path of the file storing the collection properties."""
+-        return self._path + ".props"
++        return self._filesystem_path + ".props"
+ 
+     def _create_dirs(self):
+         """Create folder storing the collection if absent."""
+-        if not os.path.exists(os.path.dirname(self._path)):
+-            os.makedirs(os.path.dirname(self._path))
++        if not os.path.exists(os.path.dirname(self._filesystem_path)):
++            os.makedirs(os.path.dirname(self._filesystem_path))
+ 
+     def save(self, text):
+         self._create_dirs()
+-        with open(self._path, "w") as fd:
++        with open(self._filesystem_path, "w") as fd:
+             fd.write(text)
+ 
+     def delete(self):
+-        os.remove(self._path)
++        os.remove(self._filesystem_path)
+         os.remove(self._props_path)
+ 
+     @property
+     def text(self):
+         try:
+-            with open(self._path) as fd:
++            with open(self._filesystem_path) as fd:
+                 return fd.read()
+         except IOError:
+             return ""
+ 
+     @classmethod
+     def children(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        _, directories, files = next(os.walk(abs_path))
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        _, directories, files = next(os.walk(filesystem_path))
+         for filename in directories + files:
++            # make sure that the local filename can be translated
++            # into an internal path
++            if not pathutils.is_safe_path_component(filename):
++                log.LOGGER.debug("Skipping unsupported filename: %s",
++                                 filename)
++                continue
+             rel_filename = posixpath.join(path, filename)
+             if cls.is_node(rel_filename) or cls.is_leaf(rel_filename):
+                 yield cls(rel_filename)
+ 
+     @classmethod
+     def is_node(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        return os.path.isdir(abs_path)
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        return os.path.isdir(filesystem_path)
+ 
+     @classmethod
+     def is_leaf(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        return os.path.isfile(abs_path) and not abs_path.endswith(".props")
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        return (os.path.isfile(filesystem_path) and not
++                filesystem_path.endswith(".props"))
+ 
+     @property
+     def last_modified(self):
+-        modification_time = time.gmtime(os.path.getmtime(self._path))
++        modification_time = \
++            time.gmtime(os.path.getmtime(self._filesystem_path))
+         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time)
+ 
+     @property
+diff --git a/radicale/storage/multifilesystem.py 
b/radicale/storage/multifilesystem.py
+index ca2d70c..f3e0476 100644
+--- a/radicale/storage/multifilesystem.py
++++ b/radicale/storage/multifilesystem.py
+@@ -29,13 +29,14 @@ import sys
+ 
+ from . import filesystem
+ from .. import ical
++from .. import pathutils
+ 
+ 
+ class Collection(filesystem.Collection):
+     """Collection stored in several files per calendar."""
+     def _create_dirs(self):
+-        if not os.path.exists(self._path):
+-            os.makedirs(self._path)
++        if not os.path.exists(self._filesystem_path):
++            os.makedirs(self._filesystem_path)
+ 
+     @property
+     def headers(self):
+@@ -54,16 +55,27 @@ class Collection(filesystem.Collection):
+             name = (
+                 component.name if sys.version_info[0] >= 3 else
+                 component.name.encode(filesystem.FILESYSTEM_ENCODING))
+-            path = os.path.join(self._path, name)
+-            with filesystem.open(path, "w") as fd:
++            if not pathutils.is_safe_filesystem_path_component(name):
++                log.LOGGER.debug(
++                        "Can't tranlate name safely to filesystem, "
++                        "skipping component: %s", name)
++                continue
++            filesystem_path = os.path.join(self._filesystem_path, name)
++            with filesystem.open(filesystem_path, "w") as fd:
+                 fd.write(text)
+ 
+     def delete(self):
+-        shutil.rmtree(self._path)
++        shutil.rmtree(self._filesystem_path)
+ 
+     def remove(self, name):
+-        if os.path.exists(os.path.join(self._path, name)):
+-            os.remove(os.path.join(self._path, name))
++        if not pathutils.is_safe_filesystem_path_component(name):
++            log.LOGGER.debug(
++                    "Can't tranlate name safely to filesystem, "
++                    "skipping component: %s", name)
++            return
++        filesystem_path = os.path.join(self._filesystem_path, name)
++        if os.path.exists(filesystem_path):
++            os.remove(filesystem_path)
+ 
+     @property
+     def text(self):
+@@ -71,8 +83,9 @@ class Collection(filesystem.Collection):
+             ical.Timezone, ical.Event, ical.Todo, ical.Journal, ical.Card)
+         items = set()
+         try:
+-            for filename in os.listdir(self._path):
+-                with filesystem.open(os.path.join(self._path, filename)) as 
fd:
++            for filename in os.listdir(self._filesystem_path):
++                filesystem_path = os.path.join(self._filesystem_path, 
filename)
++                with filesystem.open(filesystem_path, "w") as fd:
+                     items.update(self._parse(fd.read(), components))
+         except IOError:
+             return ""
+@@ -82,17 +95,21 @@ class Collection(filesystem.Collection):
+ 
+     @classmethod
+     def is_node(cls, path):
+-        path = os.path.join(filesystem.FOLDER, path.replace("/", os.sep))
+-        return os.path.isdir(path) and not os.path.exists(path + ".props")
++        filesystem_path = pathutils.path_to_filesystem(path,
++                                                       filesystem.FOLDER)
++        return (os.path.isdir(filesystem_path) and
++                not os.path.exists(filesystem_path + ".props"))
+ 
+     @classmethod
+     def is_leaf(cls, path):
+-        path = os.path.join(filesystem.FOLDER, path.replace("/", os.sep))
+-        return os.path.isdir(path) and os.path.exists(path + ".props")
++        filesystem_path = pathutils.path_to_filesystem(path,
++                                                       filesystem.FOLDER)
++        return (os.path.isdir(filesystem_path) and
++                os.path.exists(path + ".props"))
+ 
+     @property
+     def last_modified(self):
+         last = max([
+-            os.path.getmtime(os.path.join(self._path, filename))
+-            for filename in os.listdir(self._path)] or [0])
++            os.path.getmtime(os.path.join(self._filesystem_path, filename))
++            for filename in os.listdir(self._filesystem_path)] or [0])
+         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(last))
diff -Nru radicale-0.9/debian/patches/series radicale-0.9/debian/patches/series
--- radicale-0.9/debian/patches/series  2013-12-28 02:59:42.000000000 +0100
+++ radicale-0.9/debian/patches/series  2016-01-28 17:50:28.000000000 +0100
@@ -1,3 +1,4 @@
 1001_comment_out_defaults.patch
 1002_daemon-owned_logdir.patch
 2001_debianize_config.patch
+CVE-2015-8748-and-CVE-2015-8747.patch
diff -Nru radicale-0.7/debian/changelog radicale-0.7/debian/changelog
--- radicale-0.7/debian/changelog       2013-02-11 14:06:19.000000000 +0100
+++ radicale-0.7/debian/changelog       2016-01-28 17:55:21.000000000 +0100
@@ -1,3 +1,14 @@
+radicale (0.7-1.1+deb7u1) wheezy-security; urgency=high
+
+  * Non-maintainer upload by the Security Team.
+  * CVE-2015-8748 and CVE-2015-8747:
+    Fix insecure path handling by sanitizing system paths and always
+    making them absolute. Version 0.7 of Radicale is only partly affected by
+    CVE-2015-8747 because the multifilesystem storage does not exist in
+    this version. (Closes: #809920)
+
+ -- Markus Koschany <a...@debian.org>  Thu, 28 Jan 2016 14:42:55 +0100
+
 radicale (0.7-1.1) unstable; urgency=low
 
   * Non-maintainer upload. (Acknowledged by Jonas Smedegaard)
diff -Nru radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 
radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch
--- radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch   
1970-01-01 01:00:00.000000000 +0100
+++ radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch   
2016-01-28 17:55:21.000000000 +0100
@@ -0,0 +1,285 @@
+From: Markus Koschany <a...@debian.org>
+Date: Thu, 28 Jan 2016 14:39:42 +0100
+Subject: CVE-2015-8748 and CVE-2015-8747
+
+Fix insecure path handling by sanitizing system paths and always
+making them absolute. The patch was kindly reviewed by upstream. Version 0.7 of
+Radicale is only partly affected by CVE-2015-8747 because the multifilesystem 
storage
+does not exist in this version.
+
+Origin: https://github.com/Kozea/Radicale/pull/343/commits
+Debian-Bug: https://bugs.debian.org/809920
+---
+ radicale/__init__.py           |  9 ++---
+ radicale/ical.py               | 11 +++---
+ radicale/pathutils.py          | 84 ++++++++++++++++++++++++++++++++++++++++++
+ radicale/storage/filesystem.py | 43 +++++++++++++--------
+ 4 files changed, 120 insertions(+), 27 deletions(-)
+ create mode 100644 radicale/pathutils.py
+
+diff --git a/radicale/__init__.py b/radicale/__init__.py
+index 2ed1b1e..fe71e83 100644
+--- a/radicale/__init__.py
++++ b/radicale/__init__.py
+@@ -31,7 +31,6 @@ should have been included in this package.
+ import os
+ import pprint
+ import base64
+-import posixpath
+ import socket
+ import ssl
+ import wsgiref.simple_server
+@@ -47,6 +46,7 @@ except ImportError:
+ # pylint: enable=F0401,E0611
+ 
+ from radicale import acl, config, ical, log, storage, xmlutils
++from . import pathutils
+ 
+ 
+ VERSION = "0.7"
+@@ -162,12 +162,9 @@ class Application(object):
+ 
+     @staticmethod
+     def sanitize_uri(uri):
+-        """Unquote and remove /../ to prevent access to other data."""
++        """Unquote and make absolute to prevent access to other data."""
+         uri = unquote(uri)
+-        trailing_slash = "/" if uri.endswith("/") else ""
+-        uri = posixpath.normpath(uri)
+-        trailing_slash = "" if uri == "/" else trailing_slash
+-        return uri + trailing_slash
++        return pathutils.sanitize_path(uri)
+ 
+     def __call__(self, environ, start_response):
+         """Manage a request."""
+diff --git a/radicale/ical.py b/radicale/ical.py
+index d4608c7..f3a4357 100644
+--- a/radicale/ical.py
++++ b/radicale/ical.py
+@@ -26,9 +26,9 @@ Define the main classes of a collection as seen from the 
server.
+ """
+ 
+ import os
+-import posixpath
+ import uuid
+ from contextlib import contextmanager
++from . import pathutils
+ 
+ 
+ def serialize(tag, headers=(), items=()):
+@@ -176,8 +176,9 @@ class Collection(object):
+ 
+         """
+         self.encoding = "utf-8"
+-        split_path = path.split("/")
+-        self.path = path if path != "." else ""
++        # path should already be sanitized
++        self.path = pathutils.sanitize_path(path).strip("/")
++        split_path = self.path.split("/")
+         if principal and split_path and self.is_node(self.path):
+             # Already existing principal collection
+             self.owner = split_path[0]
+@@ -203,8 +204,8 @@ class Collection(object):
+         The ``path`` is relative.
+ 
+         """
+-        # First do normpath and then strip, to prevent access to FOLDER/../
+-        sane_path = posixpath.normpath(path.replace(os.sep, "/")).strip("/")
++        # path should already be sanitized
++        sane_path = pathutils.sanitize_path(path).strip("/")
+         attributes = sane_path.split("/")
+         if not attributes:
+             return None
+diff --git a/radicale/pathutils.py b/radicale/pathutils.py
+new file mode 100644
+index 0000000..2aa13af
+--- /dev/null
++++ b/radicale/pathutils.py
+@@ -0,0 +1,84 @@
++# -*- coding: utf-8 -*-
++#
++# This file is part of Radicale Server - Calendar Server
++#
++# This library is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# This library is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
++
++"""
++Helper functions for working with paths
++
++"""
++
++import os
++import posixpath
++
++from . import log
++
++
++def sanitize_path(path):
++    """Make absolute (with leading slash) to prevent access to other data.
++       Preserves an potential trailing slash."""
++    trailing_slash = "/" if path.endswith("/") else ""
++    path = posixpath.normpath(path)
++    new_path = "/"
++    for part in path.split("/"):
++        if not part or part in (".", ".."):
++            continue
++        new_path = posixpath.join(new_path, part)
++    trailing_slash = "" if new_path.endswith("/") else trailing_slash
++    return new_path + trailing_slash
++
++
++def is_safe_path_component(path):
++    """Checks if path is a single component of a path and is safe to join"""
++    if not path:
++        return False
++    head, _ = posixpath.split(path)
++    if head:
++        return False
++    if path in (".", ".."):
++        return False
++    return True
++
++
++def is_safe_filesystem_path_component(path):
++    """Checks if path is a single component of a local filesystem path
++       and is safe to join"""
++    if not path:
++        return False
++    drive, _ = os.path.splitdrive(path)
++    if drive:
++        return False
++    head, _ = os.path.split(path)
++    if head:
++        return False
++    if path in (os.curdir, os.pardir):
++        return False
++    return True
++
++
++def path_to_filesystem(path, base_folder):
++    """Converts path to a local filesystem path relative to base_folder
++        in a secure manner or raises ValueError."""
++    sane_path = sanitize_path(path).strip("/")
++    safe_path = base_folder
++    if not sane_path:
++        return safe_path
++    for part in sane_path.split("/"):
++        if not is_safe_filesystem_path_component(part):
++            log.LOGGER.debug("Can't translate path safely to filesystem: %s",
++                             path)
++            raise ValueError("Unsafe path")
++        safe_path = os.path.join(safe_path, part)
++    return safe_path
+\ No newline at end of file
+diff --git a/radicale/storage/filesystem.py b/radicale/storage/filesystem.py
+index 546a32f..d7ffa87 100644
+--- a/radicale/storage/filesystem.py
++++ b/radicale/storage/filesystem.py
+@@ -23,12 +23,13 @@ Filesystem storage backend.
+ 
+ import codecs
+ import os
+-import posixpath
+ import json
+ import time
+ from contextlib import contextmanager
+ 
+ from radicale import config, ical
++from .. import pathutils
++
+ 
+ 
+ FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder"))
+@@ -46,57 +47,67 @@ def open(path, mode="r"):
+ class Collection(ical.Collection):
+     """Collection stored in a flat ical file."""
+     @property
+-    def _path(self):
++    def _filesystem_path(self):
+         """Absolute path of the file at local ``path``."""
+-        return os.path.join(FOLDER, self.path.replace("/", os.sep))
++        return pathutils.path_to_filesystem(self.path, FOLDER)
+ 
+     @property
+     def _props_path(self):
+         """Absolute path of the file storing the collection properties."""
+-        return self._path + ".props"
++        return self._filesystem_path + ".props"
+ 
+     def _create_dirs(self):
+         """Create folder storing the collection if absent."""
+-        if not os.path.exists(os.path.dirname(self._path)):
+-            os.makedirs(os.path.dirname(self._path))
++        if not os.path.exists(os.path.dirname(self._filesystem_path)):
++            os.makedirs(os.path.dirname(self._filesystem_path))
+ 
+     def save(self, text):
+         self._create_dirs()
+-        open(self._path, "w").write(text)
++        with open(self._filesystem_path, "w") as fd:
++            fd.write(text)
+ 
+     def delete(self):
+-        os.remove(self._path)
++        os.remove(self._filesystem_path)
+         os.remove(self._props_path)
+ 
+     @property
+     def text(self):
+         try:
+-            return open(self._path).read()
++            with open(self._filesystem_path) as fd:
++                return fd.read()
+         except IOError:
+             return ""
+ 
+     @classmethod
+     def children(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        _, directories, files = next(os.walk(abs_path))
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        _, directories, files = next(os.walk(filesystem_path))
+         for filename in directories + files:
++            # make sure that the local filename can be translated
++            # into an internal path
++            if not pathutils.is_safe_path_component(filename):
++                log.LOGGER.debug("Skipping unsupported filename: %s",
++                                 filename)
++                continue
+             rel_filename = posixpath.join(path, filename)
+             if cls.is_node(rel_filename) or cls.is_leaf(rel_filename):
+                 yield cls(rel_filename)
+ 
+     @classmethod
+     def is_node(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        return os.path.isdir(abs_path)
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        return os.path.isdir(filesystem_path)
+ 
+     @classmethod
+     def is_leaf(cls, path):
+-        abs_path = os.path.join(FOLDER, path.replace("/", os.sep))
+-        return os.path.isfile(abs_path) and not abs_path.endswith(".props")
++        filesystem_path = pathutils.path_to_filesystem(path, FOLDER)
++        return (os.path.isfile(filesystem_path) and not
++                filesystem_path.endswith(".props"))
+ 
+     @property
+     def last_modified(self):
+-        modification_time = time.gmtime(os.path.getmtime(self._path))
++        modification_time = \
++            time.gmtime(os.path.getmtime(self._filesystem_path))
+         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time)
+ 
+     @property
diff -Nru radicale-0.7/debian/patches/series radicale-0.7/debian/patches/series
--- radicale-0.7/debian/patches/series  2013-02-11 14:06:19.000000000 +0100
+++ radicale-0.7/debian/patches/series  2016-01-28 17:55:21.000000000 +0100
@@ -3,3 +3,4 @@
 1001_comment_out_defaults.patch
 1002_daemon-owned_logdir.patch
 2001_debianize_config.patch
+CVE-2015-8748-and-CVE-2015-8747.patch

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to