Re: Segfault in name mapper

Jon Siddle <[email protected]> Wed, 15 Sep 2010 09:51:45 +0100
Newsgroups gmane.comp.python.cheetah
Organization Corefiling Ltd
Message-ID <[email protected]>
  On 15/09/10 04:32, R. Tyler Croy wrote:
> On Mon, 13 Sep 2010, Jon Siddle wrote:
>
>>    It is possible to trivially cause the name mapper to segfault if
>> getattr throws an exception:
>
> I just sat down to try to apply this patch and I realized it looks like you
> passed along the output from git-diff(1). Would you mind using
> git-format-patch(1) to generate a commit patch? This will allow your commit
> message and attribution to be properly carried along.

Oops. I'm fairly new to git I'm afraid. See attached.
>
>> $a.b.c
>>
>> If a.__getattr__('b') throws an exception, the name mapper will
>> segfault. This is caused by the
>> following code in PyNamemapper_valueForName():
>>
>> nextVal = PyObject_GetAttrString(currentVal, currentKey);
>> if (exc != NULL) {
>>       // if exception == AttributeError
>>       if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
>>           setNotFoundException(currentKey, currentVal);
>>           if (i>  0) {
>>               Py_DECREF(currentVal);
>>           }
>>           return NULL;
>>       }
>> }
>>
>> As you can see, only AttributeError is handled. Any other exception
>> will cause currentVal to be NULL
>> the next time round the loop and the call to
>> PyObject_GetAttrString(currentVal, currentKey) will segfault.
>>
>> The solution is simply to move the decref-return code outside the
>> check for AttributeError so that it happens
>> for all exceptions.
>>
>> The attached patch fixes the bug, and adds a test for it. I've added
>> a return code check to the buildandrun
>> script so the segfault doesn't go unnoticed when the test fails.
>>
>> FYI We encountered this when using the sqlalchemy ORM, which can
>> throw exceptions from __getattr__ on
>> objects in the session, so it's quite likely that others will
>> encounter this.
>>
>> Thanks
>>
>> Jon
>>
>> -- 
>> Jon Siddle, CoreFiling Limited
>> Software Tools Developer
>> http://www.corefiling.com
>> Phone: +44-1865-203192
>> diff --git a/buildandrun b/buildandrun
>> index d3b4e42..41a636a 100755
>> --- a/buildandrun
>> +++ b/buildandrun
>> @@ -58,7 +58,8 @@ def main():
>>       os.putenv('PYTHONPATH', libdir)
>>
>>       rc = subprocess.call( ['python',] + args )
>> -
>> +    if rc == -11:
>> +        logging.error('Segmentation fault in test process. Test failed.')
>>
>>
>>   if __name__ == '__main__':
>> diff --git a/cheetah/Tests/NameMapper.py b/cheetah/Tests/NameMapper.py
>> index ac42244..c44fb4c 100644
>> --- a/cheetah/Tests/NameMapper.py
>> +++ b/cheetah/Tests/NameMapper.py
>> @@ -43,6 +43,10 @@ class DummyClass(object):
>>           except:
>>               raise
>>
>> +class DummyClassGetAttrRaises(object):
>> +    def __getattr__(self, name):
>> +        raise ValueError
>> +
>>
>>   def dummyFunc(arg="Scooby"):
>>       return arg
>> @@ -67,6 +71,7 @@ testNamespace = {
>>       'aClass': DummyClass,
>>       'aFunc': dummyFunc,
>>       'anObj': DummyClass(),
>> +    'anObjThatRaises': DummyClassGetAttrRaises(),
>>       'aMeth': DummyClass().meth1,
>>       'none': None,
>>       'emptyString': '',
>> @@ -419,6 +424,14 @@ class VFN(NameMapperTest):
>>           for i in range(10):
>>               self.get('aDict.nestedDict.funcThatRaises', False)
>>
>> +    def test61(self):
>> +        """Accessing attribute where __getattr__ raises shouldn't segfault if something follows it"""
>> +
>> +        def test(self=self):
>> +            self.get('anObjThatRaises.willraise.anything')
>> +        self.assertRaises(ValueError, test)
>> +
>> +
>>   class VFS(VFN):
>>       _searchListLength = 1
>>
>> diff --git a/cheetah/c/_namemapper.c b/cheetah/c/_namemapper.c
>> index a41571b..a114658 100644
>> --- a/cheetah/c/_namemapper.c
>> +++ b/cheetah/c/_namemapper.c
>> @@ -188,14 +188,15 @@ static PyObject *PyNamemapper_valueForName(PyObject *obj, char *nameChunks[], in
>>             nextVal = PyObject_GetAttrString(currentVal, currentKey);
>>             exc = PyErr_Occurred();
>>             if (exc != NULL) {
>> -            // if exception == AttributeError
>> +            // if exception == AttributeError, report our own exception
>>               if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
>>                   setNotFoundException(currentKey, currentVal);
>> -                if (i>  0) {
>> -                    Py_DECREF(currentVal);
>> -                }
>> -                return NULL;
>>               }
>> +            // any exceptions results in failure
>> +            if (i>  0) {
>> +                Py_DECREF(currentVal);
>> +            }
>> +            return NULL;
>>             }
>>           }
>>           if (i>  0) {
>>
>> ------------------------------------------------------------------------------
>> Start uncovering the many advantages of virtual appliances
>> and start using them to simplify application deployment and
>> accelerate your shift to cloud computing
>> http://p.sf.net/sfu/novell-sfdev2dev
>> _______________________________________________
>> Cheetahtemplate-discuss mailing list
>> [email protected]
>> https://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
> - R. Tyler Croy
> --------------------------------------
>    GitHub: http://github.com/rtyler
>   Twitter: http://twitter.com/agentdero
>


-- 
Jon Siddle, CoreFiling Limited
Software Tools Developer
http://www.corefiling.com
Phone: +44-1865-203192

------------------------------------------------------------------------------
Start uncovering the many advantages of virtual appliances
and start using them to simplify application deployment and
accelerate your shift to cloud computing.
http://p.sf.net/sfu/novell-sfdev2dev

_______________________________________________
Cheetahtemplate-discuss mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
0001-Added-test-and-fix-for-segfault-in-namemapper-where-.patch (text/plain, 2.8 KB)
>From 19da0cb29f143a1559a1776dab7f6a01aef863ab Mon Sep 17 00:00:00 2001
From: Jon Siddle <[email protected]>
Date: Tue, 14 Sep 2010 15:08:26 +0100
Subject: [PATCH] Added test and fix for segfault in namemapper where exception is thrown
 by __getattr__.

---
 buildandrun                 |    3 ++-
 cheetah/Tests/NameMapper.py |   13 +++++++++++++
 cheetah/c/_namemapper.c     |   11 ++++++-----
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/buildandrun b/buildandrun
index d3b4e42..41a636a 100755
--- a/buildandrun
+++ b/buildandrun
@@ -58,7 +58,8 @@ def main():
     os.putenv('PYTHONPATH', libdir)
 
     rc = subprocess.call( ['python',] + args )
-
+    if rc == -11:
+        logging.error('Segmentation fault in test process. Test failed.')
 
 
 if __name__ == '__main__':
diff --git a/cheetah/Tests/NameMapper.py b/cheetah/Tests/NameMapper.py
index ac42244..c44fb4c 100644
--- a/cheetah/Tests/NameMapper.py
+++ b/cheetah/Tests/NameMapper.py
@@ -43,6 +43,10 @@ class DummyClass(object):
         except:
             raise
 
+class DummyClassGetAttrRaises(object):
+    def __getattr__(self, name):
+        raise ValueError
+
 
 def dummyFunc(arg="Scooby"):
     return arg
@@ -67,6 +71,7 @@ testNamespace = {
     'aClass': DummyClass,    
     'aFunc': dummyFunc,
     'anObj': DummyClass(),
+    'anObjThatRaises': DummyClassGetAttrRaises(),
     'aMeth': DummyClass().meth1,
     'none': None,  
     'emptyString': '',
@@ -419,6 +424,14 @@ class VFN(NameMapperTest):
         for i in range(10):
             self.get('aDict.nestedDict.funcThatRaises', False)    
 
+    def test61(self):
+        """Accessing attribute where __getattr__ raises shouldn't segfault if something follows it"""
+
+        def test(self=self):
+            self.get('anObjThatRaises.willraise.anything')
+        self.assertRaises(ValueError, test)
+
+
 class VFS(VFN):
     _searchListLength = 1
     
diff --git a/cheetah/c/_namemapper.c b/cheetah/c/_namemapper.c
index a41571b..a114658 100644
--- a/cheetah/c/_namemapper.c
+++ b/cheetah/c/_namemapper.c
@@ -188,14 +188,15 @@ static PyObject *PyNamemapper_valueForName(PyObject *obj, char *nameChunks[], in
           nextVal = PyObject_GetAttrString(currentVal, currentKey);
           exc = PyErr_Occurred();
           if (exc != NULL) {
-            // if exception == AttributeError
+            // if exception == AttributeError, report our own exception
             if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
                 setNotFoundException(currentKey, currentVal);
-                if (i > 0) {
-                    Py_DECREF(currentVal);
-                }
-                return NULL;
             }
+            // any exceptions results in failure
+            if (i > 0) {
+                Py_DECREF(currentVal);
+            }
+            return NULL;
           }
         }
         if (i > 0) {
-- 
1.6.6.1