From abbc2f4c4b2bcb7befbe216349aca3c3f9b47e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 14 Oct 2025 10:50:02 +0200 Subject: [PATCH 1/3] fix(dav): Restrict properties allowed object classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index 8fc6b5596e677..b24b134aa8c68 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -565,6 +565,19 @@ private function encodeValueForDatabase(string $path, string $name, mixed $value $valueType = self::PROPERTY_TYPE_HREF; $value = $value->getHref(); } else { + if (!is_object($value)) { + throw new DavException( + "Property \"$name\" has an invalid value of type " . gettype($value), + ); + } + if (!str_starts_with($value::class, 'Sabre\\DAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'Sabre\\CalDAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'Sabre\\CardDAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'OCA\\DAV\\')) { + throw new DavException( + "Property \"$name\" has an invalid value of class " . $value::class, + ); + } $valueType = self::PROPERTY_TYPE_OBJECT; // serialize produces null character // these can not be properly stored in some databases and need to be replaced @@ -576,20 +589,22 @@ private function encodeValueForDatabase(string $path, string $name, mixed $value /** * @return mixed|Complex|string */ - private function decodeValueFromDatabase(string $value, int $valueType) { + private function decodeValueFromDatabase(string $value, int $valueType): mixed { switch ($valueType) { case self::PROPERTY_TYPE_XML: return new Complex($value); case self::PROPERTY_TYPE_HREF: return new Href($value); case self::PROPERTY_TYPE_OBJECT: + if (!preg_match('/^O\:\d+\:\"(OCA\\\\DAV\\\\|Sabre\\\\(Cal|Card)?DAV\\\\Xml\\\\Property\\\\)/', $value)) { + throw new \LogicException('Found an object class serialized in DB that is not allowed'); + } // some databases can not handel null characters, these are custom encoded during serialization // this custom encoding needs to be first reversed before unserializing return unserialize(str_replace('\x00', chr(0), $value)); - case self::PROPERTY_TYPE_STRING: default: return $value; - } + }; } private function encodeDefaultCalendarUrl(Href $value): Href { From a8df5c5ea8a777655bc2e96f679846da6e91dd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 14 Oct 2025 17:50:53 +0200 Subject: [PATCH 2/3] fix(dav): Allow arrays (of scalars) in property values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index b24b134aa8c68..f57325bdc1ca8 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -565,7 +565,16 @@ private function encodeValueForDatabase(string $path, string $name, mixed $value $valueType = self::PROPERTY_TYPE_HREF; $value = $value->getHref(); } else { - if (!is_object($value)) { + if (is_array($value)) { + // For array only allow scalar values + foreach ($value as $item) { + if (!is_scalar($item)) { + throw new DavException( + "Property \"$name\" has an invalid value of array containing " . gettype($value), + ); + } + } + } elseif (!is_object($value)) { throw new DavException( "Property \"$name\" has an invalid value of type " . gettype($value), ); @@ -596,6 +605,10 @@ private function decodeValueFromDatabase(string $value, int $valueType): mixed { case self::PROPERTY_TYPE_HREF: return new Href($value); case self::PROPERTY_TYPE_OBJECT: + if (preg_match('/^a:/', $value)) { + // Array, unserialize only scalar values + return unserialize(str_replace('\x00', chr(0), $value), ['allowed_classes' => false]); + } if (!preg_match('/^O\:\d+\:\"(OCA\\\\DAV\\\\|Sabre\\\\(Cal|Card)?DAV\\\\Xml\\\\Property\\\\)/', $value)) { throw new \LogicException('Found an object class serialized in DB that is not allowed'); } From 36ce3160e66b4c1964b36f3fdb0a3478a2fdeac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 16 Oct 2025 09:32:23 +0200 Subject: [PATCH 3/3] fix(dav): Allow array of array of scalars, and fix error message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 37 ++++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index f57325bdc1ca8..78886bad1160a 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -531,6 +531,18 @@ private function formatPath(string $path): string { return $path; } + private static function checkIsArrayOfScalar(string $name, array $array): void { + foreach ($array as $item) { + if (is_array($item)) { + self::checkIsArrayOfScalar($name, $item); + } elseif ($item !== null && !is_scalar($item)) { + throw new DavException( + "Property \"$name\" has an invalid value of array containing " . gettype($item), + ); + } + } + } + /** * @throws ParseException If parsing a \Sabre\DAV\Xml\Property\Complex value fails * @throws DavException If the property value is invalid @@ -567,25 +579,20 @@ private function encodeValueForDatabase(string $path, string $name, mixed $value } else { if (is_array($value)) { // For array only allow scalar values - foreach ($value as $item) { - if (!is_scalar($item)) { - throw new DavException( - "Property \"$name\" has an invalid value of array containing " . gettype($value), - ); - } - } + self::checkIsArrayOfScalar($name, $value); } elseif (!is_object($value)) { throw new DavException( "Property \"$name\" has an invalid value of type " . gettype($value), ); - } - if (!str_starts_with($value::class, 'Sabre\\DAV\\Xml\\Property\\') - && !str_starts_with($value::class, 'Sabre\\CalDAV\\Xml\\Property\\') - && !str_starts_with($value::class, 'Sabre\\CardDAV\\Xml\\Property\\') - && !str_starts_with($value::class, 'OCA\\DAV\\')) { - throw new DavException( - "Property \"$name\" has an invalid value of class " . $value::class, - ); + } else { + if (!str_starts_with($value::class, 'Sabre\\DAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'Sabre\\CalDAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'Sabre\\CardDAV\\Xml\\Property\\') + && !str_starts_with($value::class, 'OCA\\DAV\\')) { + throw new DavException( + "Property \"$name\" has an invalid value of class " . $value::class, + ); + } } $valueType = self::PROPERTY_TYPE_OBJECT; // serialize produces null character