Skip to content

Commit 4189b67

Browse files
committed
python#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.
1 parent 503baf9 commit 4189b67

File tree

4 files changed

+56
-2
lines changed

4 files changed

+56
-2
lines changed

Doc/library/netrc.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ the Unix :program:`ftp` program and other FTP clients.
2121
no argument is given, the file :file:`.netrc` in the user's home directory will
2222
be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic
2323
information including the file name, line number, and terminating token.
24+
If no argument is specified on a POSIX system, the presence of passwords in
25+
the :file:`.netrc` file will raise a :exc:`NetrcParseError` if the file
26+
ownership or permissions are insecure (owned by a user other than the user
27+
running the process, or accessible for read or write by any other user).
28+
This implements security behavior equivalent to that of ftp and other
29+
programs that use :file:`.netrc`.
2430

2531

2632
.. exception:: NetrcParseError

Lib/netrc.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Module and documentation by Eric S. Raymond, 21 Dec 1998
44

5-
import os, shlex
5+
import os, stat, shlex, pwd
66

77
__all__ = ["netrc", "NetrcParseError"]
88

@@ -21,6 +21,7 @@ def __str__(self):
2121

2222
class netrc:
2323
def __init__(self, file=None):
24+
default_netrc = file is None
2425
if file is None:
2526
try:
2627
file = os.path.join(os.environ['HOME'], ".netrc")
@@ -77,6 +78,26 @@ def __init__(self, file=None):
7778
elif tt == 'account':
7879
account = lexer.get_token()
7980
elif tt == 'password':
81+
if os.name == 'posix' and default_netrc:
82+
prop = os.fstat(fp.fileno())
83+
if prop.st_uid != os.getuid():
84+
try:
85+
fowner = pwd.getpwuid(prop.st_uid)[0]
86+
except KeyError:
87+
fowner = 'uid %s' % prop.st_uid
88+
try:
89+
user = pwd.getpwuid(os.getuid())[0]
90+
except KeyError:
91+
user = 'uid %s ' % os.getuid()
92+
raise NetrcParseError(
93+
("~/.netrc file owner (%s) does not match"
94+
" current user (%s)") % (fowner, user),
95+
file, lexer.lineno)
96+
if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
97+
raise NetrcParseError(
98+
"~/.netrc access too permissive: access"
99+
" permissions must restrict access to only"
100+
" the owner", file, lexer.lineno)
80101
password = lexer.get_token()
81102
else:
82103
raise NetrcParseError("bad follower token %r" % tt,

Lib/test/test_netrc.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def setUp (self):
3232

3333
def tearDown (self):
3434
del self.netrc
35-
os.unlink(temp_filename)
35+
test_support.unlink(temp_filename)
3636

3737
def test_case_1(self):
3838
self.assert_(self.netrc.macros == {'macro1':['line1\n', 'line2\n'],
@@ -41,6 +41,27 @@ def test_case_1(self):
4141
self.assert_(self.netrc.hosts['foo'] == ('log1', 'acct1', 'pass1'))
4242
self.assert_(self.netrc.hosts['default'] == ('log2', None, 'pass2'))
4343

44+
if os.name == 'posix':
45+
def test_security(self):
46+
# This test is incomplete since we are normally not run as root and
47+
# therefore can't test the file ownership being wrong.
48+
os.unlink(temp_filename)
49+
d = test_support.TESTFN
50+
try:
51+
os.mkdir(d)
52+
fn = os.path.join(d, '.netrc')
53+
with open(fn, 'wt') as f:
54+
f.write(TEST_NETRC)
55+
with test_support.EnvironmentVarGuard() as environ:
56+
environ.set('HOME', d)
57+
os.chmod(fn, 0600)
58+
self.netrc = netrc.netrc()
59+
self.test_case_1()
60+
os.chmod(fn, 0622)
61+
self.assertRaises(netrc.NetrcParseError, netrc.netrc)
62+
finally:
63+
test_support.rmtree(d)
64+
4465
def test_main():
4566
test_support.run_unittest(NetrcTestCase)
4667

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Issue #14984: On POSIX systems, when netrc is called without a filename
17+
argument (and therefore is reading the user's $HOME/.netrc file), it now
18+
enforces the same security rules as typical ftp clients: the .netrc file must
19+
be owned by the user that owns the process and must not be readable by any
20+
other user.
21+
1622
- Issue #16248: Disable code execution from the user's home directory by
1723
tkinter when the -E flag is passed to Python. Patch by Zachary Ware.
1824

0 commit comments

Comments
 (0)