Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix security issue in __version__ extra calculation (fix #328) #330

Merged
merged 5 commits into from
Jan 23, 2017

Conversation

lrq3000
Copy link
Member

@lrq3000 lrq3000 commented Dec 31, 2016

and add branch name in __version__ if available.

This should fix the security issue reported in #328 (thanks to @jwilk!) alias CVE-2016-10075.

TODO (but can be done later, we should merge ASAP a fix for this issue):

  • restore 100% coverage (need to virtualize .git folder and create 3 scenarios: no tqdm in config, head detached so commit hash directly in HEAD file, head in a branch so commit hash is in a ref file). Maybe use something like with unittest.mock.patch.dict(os.environ, PATH='/nonexistent') (courtesy of @jwilk)?

# Sanitize path of ref file
# get full path to ref file
ref_file_path = os.path.abspath(os.path.join(gitdir, ref_file))
# check that we are in git folder (by stripping the git folder from the ref file path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inconsistent capitalization of comments (PEP 8 recommends lowercase)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because the capitalized one pertains to the whole code block, whereas the lowercase pertains to only the next line (because there's not enough space to put it at the right of the code). I don't know how to make it more explicit or prettier...

__version__ += '-' + cur_hash
res = None
scriptdir = os.path.dirname(__file__)
gitdir = os.path.abspath(scriptdir+'/../.git')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no spaces around operator

else:
res = res[:8]
# Append to version string
if res:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not None can be more explicit

@lrq3000
Copy link
Member Author

lrq3000 commented Dec 31, 2016

Thanks @CrazyPython, I fixed what you spotted :)

@jwilk
Copy link

jwilk commented Dec 31, 2016

(Disclaimer: I haven't tested this; I only had a cursory look at the code.)
I think this is fine from security point of view.
However, if the user kept their local site-packages in a git repo, and installed tqdm there, you would be inspecting this unrelated repository, not the tqdm one. Oh well...
IMHO the desire of having super-exact __version__ doesn't justify complexity of this code. I'd just remove all the git-related stuff from _version.py. But it's not my call...
Typo: file file.

Copy link
Contributor

@CrazyPython CrazyPython left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An opinionated style review. This is also dearly in need of newlines, esp. between 15&16, 29&30, and 39&40.

@@ -15,18 +11,35 @@


# auto -extra based on commit hash (if not tagged as release)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 8 dictates one less newline here

gitdir = os.path.abspath(scriptdir + '/../.git')
if os.path.isdir(gitdir):
# Open the HEAD file
with open(os.path.join(gitdir,'HEAD'), 'r') as fh_head:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma

@@ -4,7 +4,8 @@
def test_version():
""" Test version string """
from tqdm import __version__
Mmpe = re.split('[.-]', __version__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a very good variable name - CamelCase and undescriptive


if (last_release is None) or (cur_hash not in last_release):
__version__ += '-' + cur_hash
res = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res is defined here...

gitdir = os.path.abspath(scriptdir + '/../.git')
if os.path.isdir(gitdir):
# Open the HEAD file
with open(os.path.join(gitdir,'HEAD'), 'r') as fh_head:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...but only used in this if statement. Either move it in (lazy evaluation) or move the checking operation out (imperative).

gitdir = os.path.abspath(scriptdir + '/../.git')
if os.path.isdir(gitdir):
# Open the HEAD file
with open(os.path.join(gitdir,'HEAD'), 'r') as fh_head:
Copy link
Contributor

@CrazyPython CrazyPython Dec 31, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'r' is not required, you decide if that's a good idea

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but better be explicit :)


if (last_release is None) or (cur_hash not in last_release):
__version__ += '-' + cur_hash
res = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to rename res to result. It's more clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to extra.

with open(os.path.join(gitdir,'HEAD'), 'r') as fh_head:
res = fh_head.readline().strip()
# If we are in a branch, HEAD will point to a file containing the latest commit
if 'ref:' in res:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In both of these branches, res is set. Yet above, res is set to another name. Two variables would be clearer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand your remark, res always aim to contain the extra = the commit hash (and if available the branch name). I changed the variable name to be more explicit. It's just that if we are in a branch, we don't get the commit hash from the HEAD file, so then it's necessary to do some more processing.

@@ -4,7 +4,8 @@
def test_version():
""" Test version string """
from tqdm import __version__
Mmpe = re.split('[.-]', __version__)
Mmpe = __version__.split()[0] # remove part after space (branch name)
Mmpe = re.split('[.-]', Mmpe) # split by dot and dash
assert 3 <= len(Mmpe) <= 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not the same as assert len(Mmpe) in (3, 4)? People usually expect reversed syntax, e.g. 4 >= len(Mmpe) >= 3.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is the same but I disagree the reversed syntax is more common, since they are mathematical operators I expect the same convention as in mathematical notation (least value first).

@lrq3000
Copy link
Member Author

lrq3000 commented Dec 31, 2016 via email

@jwilk
Copy link

jwilk commented Jan 1, 2017 via email

@lrq3000
Copy link
Member Author

lrq3000 commented Jan 1, 2017 via email

@CrazyPython
Copy link
Contributor

If I have time later, I might review some of the existing tqdm codebase, if you want me to.

@lrq3000 lrq3000 added this to the v5.0.0 milestone Jan 1, 2017
@lrq3000
Copy link
Member Author

lrq3000 commented Jan 1, 2017 via email

try:
map(int, Mmpe[:3])
map(int, version_parts[:3])
except:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want except ValueError:. This catches "errors" like SystemExit and other python internals.

extra = None
# Open config file to check if we are in tqdm project
with open(os.path.join(gitdir, 'config'), 'r') as fh_config:
if 'tqdm' in fh_config.read():

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you're just checking if 'tqdm' is anywhere in .git/config which might not be strict enough

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sandrotosi Yep I thought the same but after reading a bit about git internals, I didn't find a better way to check if we were looking at tqdm repository. Any suggestion? Maybe looking specifically for a specific entry in config file?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i looks like you have to go thru several steps: read .git/HEAD, lookup the remote for that ref in .git/config, check the remote url in .git/config if that ends with '/tqdm.git' (as you have to consider forks and other possible checkout methods).

i still think there must be an easier way to do it, and i'd look at out other "big" projects (numpy, scipy, matplotlib, and the like) do as they probably want to tackle the same problem as you do

@casperdcl casperdcl force-pushed the version-security-fix branch from 5702ac0 to 4857462 Compare January 22, 2017 14:25
@casperdcl
Copy link
Member

I agree that it's a bit much to go overboard with this, it was meant to be a small helper. I'm tempted to ignore coverage of the "extra" version bit.

@casperdcl casperdcl merged commit aec3f7d into master Jan 23, 2017
@casperdcl casperdcl deleted the version-security-fix branch January 23, 2017 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p0-bug-critical ☢ Exception rasing to-merge ↰ Imminent
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants