CVE-2014-1604: Other (NVD-CWE-Other)
value: The parser cache functionality in parsergenerator.py in RPLY (aka python-rply) before 0.7.1 allows local users to spoof cache data by pre-creating a temporary rply-*.json file with a predictable name.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Published: 2014-01-28T00:55Z
Repository: https://github.com/alex/rply
Language: Python
Files changed: rply/parsergenerator.py
Commit date: 2014-01-17 17:16:39 -0600
Commit message: The parser cache is now always handled in a per-user fashion.

This avoids issues with people providing malciious caches in a multi-user
system.

Originally reported as: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=735263

A CVE number has been requested

Diff: diff --git a/rply/parsergenerator.py b/rply/parsergenerator.py
index f8acd74..bc81f19 100644
--- a/rply/parsergenerator.py
+++ b/rply/parsergenerator.py
@@ -1,9 +1,10 @@
 import os
 import hashlib
 import json
 import random
+import stat
 import string
 import sys
 import tempfile
 import warnings
 
@@ -122,21 +123,27 @@ class ParserGenerator(object):
         g.compute_first()
         g.compute_follow()
 
         cache_file = os.path.join(
             tempfile.gettempdir(),
-            "rply-%s-%s-%s.json" % (self.VERSION, self.cache_id, self.compute_grammar_hash(g))
+            "rply-%s-%s-%s-%s.json" % (self.VERSION, os.getuid(), self.cache_id, self.compute_grammar_hash(g))
         )
         table = None
         if os.path.exists(cache_file):
             with open(cache_file) as f:
                 data = json.load(f)
-            if self.data_is_valid(g, data):
-                table = LRTable.from_cache(g, data)
+                stat_result = os.fstat(f.fileno())
+            if (
+                stat_result.st_uid == os.getuid() and
+                stat.S_IMODE(stat_result.st_mode) == 0o0600
+            ):
+                if self.data_is_valid(g, data):
+                    table = LRTable.from_cache(g, data)
         if table is None:
             table = LRTable.from_grammar(g)
-            with open(cache_file, "w") as f:
+            fd = os.open(cache_file, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o0600)
+            with os.fdopen(fd, "w") as f:
                 json.dump(self.serialize_table(table), f)
         if table.sr_conflicts:
             warnings.warn(
                 "%d shift/reduce conflict%s" % (len(table.sr_conflicts), "s" if len(table.sr_conflicts) > 1 else ""),
                 ParserGeneratorWarning,

Vulnerable code:             "rply-%s-%s-%s.json" % (self.VERSION, self.cache_id, self.compute_grammar_hash(g))
            if self.data_is_valid(g, data):
                table = LRTable.from_cache(g, data)
            with open(cache_file, "w") as f:
Fixed code: import stat
            "rply-%s-%s-%s-%s.json" % (self.VERSION, os.getuid(), self.cache_id, self.compute_grammar_hash(g))
                stat_result = os.fstat(f.fileno())
            if (
                stat_result.st_uid == os.getuid() and
                stat.S_IMODE(stat_result.st_mode) == 0o0600
            ):
                if self.data_is_valid(g, data):
                    table = LRTable.from_cache(g, data)
            fd = os.open(cache_file, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o0600)
            with os.fdopen(fd, "w") as f: