diff -Nru twig-1.16.2/debian/changelog twig-1.16.2/debian/changelog --- twig-1.16.2/debian/changelog 2015-01-27 15:59:58.000000000 +0000 +++ twig-1.16.2/debian/changelog 2015-11-16 21:02:28.000000000 +0000 @@ -1,8 +1,18 @@ -twig (1.16.2-1build1) vivid; urgency=medium +twig (1.16.2-1+deb8u1build0.15.04.1) vivid-security; urgency=medium - * No change rebuild against PHP 5.6 (phpapi-20131226). + * fake sync from Debian - -- Robie Basak Tue, 27 Jan 2015 15:59:58 +0000 + -- Tyler Hicks Mon, 16 Nov 2015 15:02:28 -0600 + +twig (1.16.2-1+deb8u1) jessie-security; urgency=high + + * gbp: Track the Jessie branch + * Backport security fixes from 1.20.0 + - forbid access to the Twig environment from templates + and internal parts of Twig_Template + - fixed limited RCEs when in sandbox mode + + -- Daniel Beyer Thu, 20 Aug 2015 11:37:24 +0200 twig (1.16.2-1) unstable; urgency=low diff -Nru twig-1.16.2/debian/gbp.conf twig-1.16.2/debian/gbp.conf --- twig-1.16.2/debian/gbp.conf 2014-10-26 01:39:37.000000000 +0000 +++ twig-1.16.2/debian/gbp.conf 2015-08-20 12:49:17.000000000 +0000 @@ -1,2 +1,3 @@ [DEFAULT] +debian-branch = jessie pristine-tar = True diff -Nru twig-1.16.2/debian/patches/0001-Fix-security-vulnerability-of-Twig-s-Sandbox-mode.patch twig-1.16.2/debian/patches/0001-Fix-security-vulnerability-of-Twig-s-Sandbox-mode.patch --- twig-1.16.2/debian/patches/0001-Fix-security-vulnerability-of-Twig-s-Sandbox-mode.patch 1970-01-01 00:00:00.000000000 +0000 +++ twig-1.16.2/debian/patches/0001-Fix-security-vulnerability-of-Twig-s-Sandbox-mode.patch 2015-08-20 12:49:17.000000000 +0000 @@ -0,0 +1,337 @@ +From: Daniel Beyer +Date: Thu, 20 Aug 2015 11:23:52 +0200 +Subject: Fix security vulnerability of Twig's Sandbox mode + +- fixed sandbox security issue + Upstream VCS: 30be07759a3de2558da5224f127d052ecf492e8f +- Prevent importing or calling reserved macro names + Upstream VCS: 7b6c0e971f832426132a21e3177e71f4eb566a02 +- Forbid access to the Twig environment from template instance + Upstream VCS: a8a125ba9b31d20e8ad50e0d1078983ed7fa41a7 +- Fix the accessibility of Twig_Template::getEnvironment method in the C ext + Upstream VCS: 22500609b69a9f17a64102d9376cb114f706ee2f +- Fix the handling of internal Twig_Template properties in the C ext + Upstream VCS: e000420096de342f3384d857bedee8295d03f973 + +Origin: upstream, https://github.com/twigphp/Twig/pull/1759 +--- + ext/twig/twig.c | 36 +++++++++++++++++++--- + lib/Twig/ExpressionParser.php | 8 ++++- + lib/Twig/Parser.php | 21 +++++++++---- + lib/Twig/Template.php | 26 ++++++++++++++-- + lib/Twig/TokenParser/From.php | 4 +++ + .../tags/macro/from_with_reserved_name.test | 9 ++++++ + .../tags/macro/import_with_reserved_nam.test | 11 +++++++ + .../Tests/Fixtures/tags/macro/reserved_name.test | 10 ++++++ + test/Twig/Tests/ParserTest.php | 2 +- + test/Twig/Tests/TemplateTest.php | 14 +++++++++ + 10 files changed, 127 insertions(+), 14 deletions(-) + create mode 100644 test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test + create mode 100644 test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test + create mode 100644 test/Twig/Tests/Fixtures/tags/macro/reserved_name.test + +diff --git a/ext/twig/twig.c b/ext/twig/twig.c +index 5c482be..a49c7c8 100644 +--- a/ext/twig/twig.c ++++ b/ext/twig/twig.c +@@ -665,6 +665,7 @@ static char *TWIG_GET_CLASS_NAME(zval *object TSRMLS_DC) + + static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) + { ++ zend_class_entry *ce; + zval *retval; + char *item; + size_t item_len; +@@ -675,12 +676,23 @@ static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, v + return 0; + } + ++ ce = *va_arg(args, zend_class_entry**); + retval = va_arg(args, zval*); + + item_len = strlen(mptr->common.function_name); + item = estrndup(mptr->common.function_name, item_len); + php_strtolower(item, item_len); + ++ if (strcmp("getenvironment", item) == 0) { ++ zend_class_entry **twig_template_ce; ++ if (zend_lookup_class("Twig_Template", strlen("Twig_Template"), &twig_template_ce TSRMLS_CC) == FAILURE) { ++ return 0; ++ } ++ if (instanceof_function(ce, *twig_template_ce TSRMLS_CC)) { ++ return 0; ++ } ++ } ++ + add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0); + + return 0; +@@ -726,7 +738,7 @@ static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name + array_init(class_methods); + array_init(class_properties); + // add all methods to self::cache[$class]['methods'] +- zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 1, class_methods); ++ zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 2, &class_ce, class_methods); + zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties); + + add_assoc_zval(class_info, "methods", class_methods); +@@ -917,7 +929,7 @@ PHP_FUNCTION(twig_template_get_attributes) + + /* + // object property +- if (Twig_Template::METHOD_CALL !== $type) { ++ if (Twig_Template::METHOD_CALL !== $type && !$object instanceof Twig_Template) { + if (isset($object->$item) || array_key_exists((string) $item, $object)) { + if ($isDefinedTest) { + return true; +@@ -931,7 +943,7 @@ PHP_FUNCTION(twig_template_get_attributes) + } + } + */ +- if (strcmp("method", type) != 0) { ++ if (strcmp("method", type) != 0 && !TWIG_INSTANCE_OF_USERLAND(object, "Twig_Template" TSRMLS_CC)) { + zval *tmp_properties, *tmp_item; + + tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC); +@@ -955,7 +967,23 @@ PHP_FUNCTION(twig_template_get_attributes) + /* + // object method + if (!isset(self::$cache[$class]['methods'])) { +- self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); ++ if ($object instanceof self) { ++ $ref = new ReflectionClass($class); ++ $methods = array(); ++ ++ foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { ++ $methodName = strtolower($refMethod->name); ++ ++ // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment ++ if ('getenvironment' !== $methodName) { ++ $methods[$methodName] = true; ++ } ++ } ++ ++ self::$cache[$class]['methods'] = $methods; ++ } else { ++ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); ++ } + } + + $call = false; +diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php +index f685bad..e6d28b2 100644 +--- a/lib/Twig/ExpressionParser.php ++++ b/lib/Twig/ExpressionParser.php +@@ -372,7 +372,13 @@ class Twig_ExpressionParser + throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename()); + } + +- $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno); ++ $name = $arg->getAttribute('value'); ++ ++ if ($this->parser->isReservedMacroName($name)) { ++ throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword', $name), $token->getLine(), $this->parser->getFilename()); ++ } ++ ++ $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; +diff --git a/lib/Twig/Parser.php b/lib/Twig/Parser.php +index 549ce2b..dd9c1fc 100644 +--- a/lib/Twig/Parser.php ++++ b/lib/Twig/Parser.php +@@ -254,19 +254,28 @@ class Twig_Parser implements Twig_ParserInterface + + public function setMacro($name, Twig_Node_Macro $node) + { ++ if ($this->isReservedMacroName($name)) { ++ throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); ++ } ++ ++ $this->macros[$name] = $node; ++ } ++ ++ public function isReservedMacroName($name) ++ { + if (null === $this->reservedMacroNames) { + $this->reservedMacroNames = array(); + $r = new ReflectionClass($this->env->getBaseTemplateClass()); + foreach ($r->getMethods() as $method) { +- $this->reservedMacroNames[] = $method->getName(); +- } +- } ++ $methodName = strtolower($method->getName()); + +- if (in_array($name, $this->reservedMacroNames)) { +- throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); ++ if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) { ++ $this->reservedMacroNames[] = substr($methodName, 3); ++ } ++ } + } + +- $this->macros[$name] = $node; ++ return in_array(strtolower($name), $this->reservedMacroNames); + } + + public function addTrait($trait) +diff --git a/lib/Twig/Template.php b/lib/Twig/Template.php +index 63910da..58ed0da 100644 +--- a/lib/Twig/Template.php ++++ b/lib/Twig/Template.php +@@ -140,6 +140,11 @@ abstract class Twig_Template implements Twig_TemplateInterface + } + + if (null !== $template) { ++ // avoid RCEs when sandbox is enabled ++ if (!$template instanceof Twig_Template) { ++ throw new \LogicException('A block must be a method on a Twig_Template instance.'); ++ } ++ + try { + $template->$block($context, $blocks); + } catch (Twig_Error $e) { +@@ -407,7 +412,7 @@ abstract class Twig_Template implements Twig_TemplateInterface + } + + // object property +- if (Twig_Template::METHOD_CALL !== $type) { ++ if (Twig_Template::METHOD_CALL !== $type && !$object instanceof self) { // Twig_Template does not have public properties, and we don't want to allow access to internal ones + if (isset($object->$item) || array_key_exists((string) $item, $object)) { + if ($isDefinedTest) { + return true; +@@ -425,7 +430,24 @@ abstract class Twig_Template implements Twig_TemplateInterface + + // object method + if (!isset(self::$cache[$class]['methods'])) { +- self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); ++ // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates ++ if ($object instanceof self) { ++ $ref = new ReflectionClass($class); ++ $methods = array(); ++ ++ foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { ++ $methodName = strtolower($refMethod->name); ++ ++ // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment ++ if ('getenvironment' !== $methodName) { ++ $methods[$methodName] = true; ++ } ++ } ++ ++ self::$cache[$class]['methods'] = $methods; ++ } else { ++ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); ++ } + } + + $call = false; +diff --git a/lib/Twig/TokenParser/From.php b/lib/Twig/TokenParser/From.php +index dd73f99..5540efa 100644 +--- a/lib/Twig/TokenParser/From.php ++++ b/lib/Twig/TokenParser/From.php +@@ -52,6 +52,10 @@ class Twig_TokenParser_From extends Twig_TokenParser + $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); + + foreach ($targets as $name => $alias) { ++ if ($this->parser->isReservedMacroName($name)) { ++ throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword', $name), $token->getLine(), $stream->getFilename()); ++ } ++ + $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); + } + +diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test b/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test +new file mode 100644 +index 0000000..6df4f5d +--- /dev/null ++++ b/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test +@@ -0,0 +1,9 @@ ++--TEST-- ++"from" tag with reserved name ++--TEMPLATE-- ++{% from 'forms.twig' import templateName %} ++--TEMPLATE(forms.twig)-- ++--DATA-- ++return array() ++--EXCEPTION-- ++Twig_Error_Syntax: "templateName" cannot be an imported macro as it is a reserved keyword in "index.twig" at line 2 +diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test b/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test +new file mode 100644 +index 0000000..e5aa749 +--- /dev/null ++++ b/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test +@@ -0,0 +1,11 @@ ++--TEST-- ++"from" tag with reserved name ++--TEMPLATE-- ++{% import 'forms.twig' as macros %} ++ ++{{ macros.parent() }} ++--TEMPLATE(forms.twig)-- ++--DATA-- ++return array() ++--EXCEPTION-- ++Twig_Error_Syntax: "parent" cannot be called as macro as it is a reserved keyword in "index.twig" at line 4 +diff --git a/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test b/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test +new file mode 100644 +index 0000000..a2dde5a +--- /dev/null ++++ b/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test +@@ -0,0 +1,10 @@ ++--TEST-- ++"macro" tag with reserved name ++--TEMPLATE-- ++{% macro parent(arg1, arg2) %} ++ parent ++{% endmacro %} ++--DATA-- ++return array() ++--EXCEPTION-- ++Twig_Error_Syntax: "parent" cannot be used as a macro name as it is a reserved keyword in "index.twig" at line 2 +diff --git a/test/Twig/Tests/ParserTest.php b/test/Twig/Tests/ParserTest.php +index b4a3abb..a451aae 100644 +--- a/test/Twig/Tests/ParserTest.php ++++ b/test/Twig/Tests/ParserTest.php +@@ -16,7 +16,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase + public function testSetMacroThrowsExceptionOnReservedMethods() + { + $parser = $this->getParser(); +- $parser->setMacro('display', $this->getMock('Twig_Node_Macro', array(), array(), '', null)); ++ $parser->setMacro('parent', $this->getMock('Twig_Node_Macro', array(), array(), '', null)); + } + + /** +diff --git a/test/Twig/Tests/TemplateTest.php b/test/Twig/Tests/TemplateTest.php +index e2f84ee..611a8b7 100644 +--- a/test/Twig/Tests/TemplateTest.php ++++ b/test/Twig/Tests/TemplateTest.php +@@ -11,6 +11,15 @@ + class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase + { + /** ++ * @expectedException LogicException ++ */ ++ public function testDisplayBlocksAcceptTemplateOnlyAsBlocks() ++ { ++ $template = $this->getMockForAbstractClass('Twig_Template', array(), '', false); ++ $template->displayBlock('foo', array(), array('foo' => array(new stdClass(), 'foo'))); ++ } ++ ++ /** + * @dataProvider getAttributeExceptions + */ + public function testGetAttributeExceptions($template, $message, $useExt) +@@ -134,6 +143,11 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase + + $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty')); + $this->assertSame('', $template->getAttribute($template1, 'empty')); ++ ++ $this->assertFalse($template->getAttribute($template1, 'env', array(), Twig_Template::ANY_CALL, true)); ++ $this->assertFalse($template->getAttribute($template1, 'environment', array(), Twig_Template::ANY_CALL, true)); ++ $this->assertFalse($template->getAttribute($template1, 'getEnvironment', array(), Twig_Template::METHOD_CALL, true)); ++ $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', array(), Twig_Template::METHOD_CALL, true)); + } + + public function getGetAttributeWithTemplateAsObject() diff -Nru twig-1.16.2/debian/patches/series twig-1.16.2/debian/patches/series --- twig-1.16.2/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ twig-1.16.2/debian/patches/series 2015-08-20 12:49:17.000000000 +0000 @@ -0,0 +1 @@ +0001-Fix-security-vulnerability-of-Twig-s-Sandbox-mode.patch