[Python-checkins] cpython (3.1): #14984: On POSIX, enforce permissions when reading default .netrc.

r.david.murray python-checkins at python.org
Wed Sep 18 13:38:29 CEST 2013


http://hg.python.org/cpython/rev/6396d1fc72da
changeset:   85739:6396d1fc72da
branch:      3.1
parent:      85718:c39f42f46a05
user:        R David Murray <rdmurray at bitdance.com>
date:        Tue Sep 17 20:30:02 2013 -0400
summary:
  #14984: On POSIX, enforce permissions when reading default .netrc.

Initial patch by Bruno Piguet.

This is implemented as if a useful .netrc file could exist without passwords,
which is possible in the general case; but in fact our netrc implementation
does not support it.  Fixing that issue will be an enhancement.

files:
  Doc/library/netrc.rst  |   8 ++++++++
  Lib/netrc.py           |  27 ++++++++++++++++++++++++---
  Lib/test/test_netrc.py |  26 +++++++++++++++++++++++---
  Misc/NEWS              |   6 ++++++
  4 files changed, 61 insertions(+), 6 deletions(-)


diff --git a/Doc/library/netrc.rst b/Doc/library/netrc.rst
--- a/Doc/library/netrc.rst
+++ b/Doc/library/netrc.rst
@@ -19,6 +19,14 @@
    no argument is given, the file :file:`.netrc` in the user's home directory will
    be read.  Parse errors will raise :exc:`NetrcParseError` with diagnostic
    information including the file name, line number, and terminating token.
+   If no argument is specified on a POSIX system, the presence of passwords in
+   the :file:`.netrc` file will raise a :exc:`NetrcParseError` if the file
+   ownership or permissions are insecure (owned by a user other than the user
+   running the process, or accessible for read or write by any other user).
+   This implements security behavior equivalent to that of ftp and other
+   programs that use :file:`.netrc`.
+
+   .. versionchanged:: 3.1.6 Added the POSIX permission check.
 
 
 .. exception:: NetrcParseError
diff --git a/Lib/netrc.py b/Lib/netrc.py
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -2,7 +2,7 @@
 
 # Module and documentation by Eric S. Raymond, 21 Dec 1998
 
-import io, os, shlex
+import io, os, shlex, stat, pwd
 
 __all__ = ["netrc", "NetrcParseError"]
 
@@ -21,6 +21,7 @@
 
 class netrc:
     def __init__(self, file=None):
+        default_netrc = file is None
         if file is None:
             try:
                 file = os.path.join(os.environ['HOME'], ".netrc")
@@ -29,9 +30,9 @@
         self.hosts = {}
         self.macros = {}
         with open(file) as fp:
-            self._parse(file, fp)
+            self._parse(file, fp, default_netrc)
 
-    def _parse(self, file, fp):
+    def _parse(self, file, fp, default_netrc):
         lexer = shlex.shlex(fp)
         lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
         lexer.commenters = lexer.commenters.replace('#', '')
@@ -86,6 +87,26 @@
                 elif tt == 'account':
                     account = lexer.get_token()
                 elif tt == 'password':
+                    if os.name == 'posix' and default_netrc:
+                        prop = os.fstat(fp.fileno())
+                        if prop.st_uid != os.getuid():
+                            try:
+                                fowner = pwd.getpwuid(prop.st_uid)[0]
+                            except KeyError:
+                                fowner = 'uid %s' % prop.st_uid
+                            try:
+                                user = pwd.getpwuid(os.getuid())[0]
+                            except KeyError:
+                                user = 'uid %s' % os.getuid()
+                            raise NetrcParseError(
+                                ("~/.netrc file owner (%s) does not match"
+                                 " current user (%s)") % (fowner, user),
+                                file, lexer.lineno)
+                        if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
+                            raise NetrcParseError(
+                               "~/.netrc access too permissive: access"
+                               " permissions must restrict access to only"
+                               " the owner", file, lexer.lineno)
                     password = lexer.get_token()
                 else:
                     raise NetrcParseError("bad follower token %r" % tt,
diff --git a/Lib/test/test_netrc.py b/Lib/test/test_netrc.py
--- a/Lib/test/test_netrc.py
+++ b/Lib/test/test_netrc.py
@@ -5,9 +5,6 @@
 
 class NetrcTestCase(unittest.TestCase):
 
-    def tearDown(self):
-        os.unlink(temp_filename)
-
     def make_nrc(self, test_data):
         test_data = textwrap.dedent(test_data)
         mode = 'w'
@@ -15,6 +12,7 @@
             mode += 't'
         with open(temp_filename, mode) as fp:
             fp.write(test_data)
+        self.addCleanup(os.unlink, temp_filename)
         return netrc.netrc(temp_filename)
 
     def test_default(self):
@@ -103,6 +101,28 @@
             """, '#pass')
 
 
+    @unittest.skipUnless(os.name == 'posix', 'POSIX only test')
+    def test_security(self):
+        # This test is incomplete since we are normally not run as root and
+        # therefore can't test the file ownership being wrong.
+        d = support.TESTFN
+        os.mkdir(d)
+        self.addCleanup(support.rmtree, d)
+        fn = os.path.join(d, '.netrc')
+        with open(fn, 'wt') as f:
+            f.write("""\
+                machine foo.domain.com login bar password pass
+                default login foo password pass
+                """)
+        with support.EnvironmentVarGuard() as environ:
+            environ.set('HOME', d)
+            os.chmod(fn, 0o600)
+            nrc = netrc.netrc()
+            self.assertEqual(nrc.hosts['foo.domain.com'],
+                             ('bar', None, 'pass'))
+            os.chmod(fn, 0o622)
+            self.assertRaises(netrc.NetrcParseError, netrc.netrc)
+
 def test_main():
     support.run_unittest(NetrcTestCase)
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,12 @@
 Library
 -------
 
+- Issue #14984: On POSIX systems, when netrc is called without a filename
+  argument (and therefore is reading the user's $HOME/.netrc file), it now
+  enforces the same security rules as typical ftp clients: the .netrc file must
+  be owned by the user that owns the process and must not be readable by any
+  other user.
+
 - Issue #16248: Disable code execution from the user's home directory by tkinter
   when the -E flag is passed to Python.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list