
A bite of Python - ashitlerferad
https://access.redhat.com/blogs/766093/posts/2592591
======
michaelfeathers
The behavior of 'assert' is not an anomaly. It comes from 'design by
contract.' Assert is primarily meant to be documentation of constraints in
code and secondarily a way of catching errors during development.

"Contract conditions should never be violated during execution of a bug-free
program. Contracts are therefore typically only checked in debug mode during
software development. Later at release, the contract checks are disabled to
maximize performance." \-
[https://en.wikipedia.org/wiki/Design_by_contract](https://en.wikipedia.org/wiki/Design_by_contract)

~~~
humanrebar
That is certainly one approach, and the article agrees.

> The root cause of this weakness is that the assert mechanism is designed
> purely for testing purposes, as is done in C++.

However, C and C++ are perhaps unique in how much undefined behavior is
possible and in how simple it is to create. Inserting into a vector while
iterating through it, for instance. Or an uninitialized pointer.

That's why many C++ experts believe in runtime assertions in production.
Crashing the application with a core dump is generally preferable to trashing
memory, corrupting your database, or launching the missiles.

~~~
p4wnc6
All of the C/C++ experts I know, as well as people who have interviewed me
coming from primarily that background, have always been among the most adamant
to stress that an application crashing unexpectedly should _never_ happen and
is _always_ the wrong outcome.

I imagine they would say that your statement about crashing vs. e.g. launching
the missiles is a false dilemma. You don't crash _and_ you don't incorrectly
launch the missiles.

I'm not a C++ developer so I can't say it with certainty. I more agree with
what you're saying. I'm just relaying that my experience has been that out of
many different language communities, C++ actually seems adamantly the opposite
of what you're describing.

~~~
dcuthbertson
I've been writing software in C and C++ for a long time. Crashing is never a
good user experience, so avoid it. If something unexpected happens, catch and
report the error, then carry on, if possible, or gracefully exit otherwise.

~~~
ianai
As someone who works in support, customers (at least the ones I support)
REALLY need a clear and obvious crash to understand something's wrong. That
really forces them to "do something differently" and/or look for help. You're
correct in that it's not a good user experience. Neither is chaos.

~~~
humanrebar
> Neither is chaos.

Exactly. "Undefined behavior" includes showing private data to the wrong user
and booking ten times more orders than the user originally indicated. I'll
take crashing over that.

------
pvdebbe
One Python gotcha that has bitten people in my company a lot:

    
    
        fun_call('string1',
                 'string2'
                 'string3')
    

That is, missing commas and subsequent string concatenations can lead to nasty
errors. I wish Python didn't nick this from C and would have just enforced the
use of + to concat over-length strings, if they need to be split to multiple
lines.

~~~
dec0dedab0de
I love Python, but I have to agree on this one.

~~~
bpicolo
I mean, this one is solvable by not using magic constants. Pretty easy to
avoid. Can also lint it.

~~~
gakada
I don't get what you're saying. How is it possible to write code without
string literals?

    
    
        subprocess.check_call([
          "/usr/bin/env",
          "echo"
          "hello world"
        ])

~~~
nitely
I think she meant to not pass the strings but assign them to constants and
then pass those.

~~~
bpicolo
Yep! But, like I mentioned, linter can catch this too.

------
viraptor
If you're interested in reviewing Python code for potential security issues,
here's a related project:
[https://github.com/openstack/bandit](https://github.com/openstack/bandit)
(I'm one of the devs)

It will actually pick up a number of security issues listed in the post. It's
useful in real world too - led to a number of CVEs being reported.

~~~
ubernostrum
I looked at Bandit earlier this year, but had to put it down when I discovered
it didn't have a way to fill in default config -- the instant I specified
_anything_ in the config file, I had to supply a _complete_ config, including
literally every single check it's capable of doing, because Bandit would
discard all its defaults on encountering that single line of custom config.

Don't suppose you know if that's gotten better?

~~~
viraptor
It's not ideal - the config file is still a complete list, but there are a few
things you can do.

\- `bandit-config-generator` will give you a file filled with the
current/default configuration, so it's a simple way to start with the defaults
and modify just what you need

\- if you just need to enable/disable tests rather than reconfigure, you can
do that in command line options

\- if you want to get rid of specific warning, you can mark the line with "#
nosec" in the source

Merging various configs is possible, but rather complex to implement
considering we aim for the config to be a complete description that won't ever
need to change between versions.

If none of the above workarounds solve your use case, feel free to report an
issue.
([https://bugs.launchpad.net/bandit](https://bugs.launchpad.net/bandit)) I
can't guarantee how/whether we'll fix this, but we'd definitely like to know
what the problem is and how you're trying to use Bandit.

~~~
wyldfire
FYI when using pypy bandit fails to discover tests for some reason (could be a
pypy bug?). In this case, the default output generated by bandit-config-
generator is mostly empty and bandit fails to parse it. It doesn't indicate
what the nature of the parse failure is (what line, what rule(s) were
violated), even with verbose mode. The readme doesn't cover the format of the
config file. Is it YAML/TOML/JSON/other?

~~~
viraptor
Which version of pypy are you using? I can't reproduce the issue with 5.1.2
(ubuntu packaged one).

The config uses YAML format.

Regardless, config errors should be more verbose. I raised
[https://bugs.launchpad.net/bandit/+bug/1621552](https://bugs.launchpad.net/bandit/+bug/1621552)

~~~
wyldfire
EDIT: nevermind, couldn't reproduce it with a new virtualenv. Whatever problem
occurred in that virtualenv likely wasn't that interesting (wild guess is a
package name collision).

pypy2-v5.3.1-linux64 / [PyPy 5.3.1 with GCC 4.8.2]

If you can't reproduce it with that tarball I'll dig deeper to see the
mechanism of failure, maybe it's not pypy and it's just something local to my
config or venv.

~~~
viraptor
Checked the same tarball (downloaded from bitbucket) and still can't repro.
Just in case, what I'm doing is:

    
    
       cd path/to/bandit
       virtualenv -p path/to/pypy venv
       venv/bin/pip install -r./requirements.txt -e .
       venv/bin/bandit-config-generator -o tmp_file
       venv/bin/bandit -c tmp_file -r path/to/some/project

~~~
wyldfire
I did something similar, yes. I cannot reproduce this with a new virtualenv
anymore. It may have been due to odd bits in my environment (likely not worth
further investigation).

    
    
        virtualenv -p `which pypy` ~/pypy_env
        source ~/pypy_env/bin/activate
        # indeterminate <but probably critical> changes to this venv
        pip install bandit
        bandit-config-generator -o tmp_file
        bandit --help # "The following sets..." is empty
        bandit -c tmp_file -r path/to/some/project # gives the error regarding config file parse failure
    

I _can_ reproduce the parse error given the nearly empty config file, but it's
not clear to me whether the parse error is expected in this case or not.

    
    
        $ echo -e 'tests:\nskips:\n{}' > parse_err.cfg
        $ bandit -c parse_err.cfg .

[main] ERROR parse_err.cfg : Error parsing file.

------
alex-yo
I wouldn't call it 'traps'. I would call it 'read and understand documentation
before writing code' like: what is 'is' operator, or how floats behave in
EVERY programming language, or why you should sanitize EVERY user input.

So, basically, I can write such a list for every language I know.

~~~
blowski
Relying on developers to read and remember every bit of documentation for
every bit of code is more likely to end up with insecure code compared to
introducing sane defaults with an explicit, expressive API.

~~~
SEJeff
And the TL;DNR being: developers can't be expected to do a good job. Some of
them in fact will do a terrible job.

This can be said for every industry involving people.

~~~
hueving
Which is why any sane industry has lots of safety involved. We don't just
shrug every time someone gets electrocuted to death and say "they forgot part
c page 4 of the operations manual which indicates that the off switch doesn't
work on tuesdays".

------
skywhopper
I would never accuse Python of "language clarity and friendliness". Far from
it. For someone who came up through C, Java, Perl, and Ruby, but who's
wrangled with Python, Javascript, Go, and even Haskell in recent years, I
still find Python mysterious, self-contradictory, and filled with implicit
rules and assumptions that are entirely un-intuitive to me far more than other
languages. And yet, people seem to like it. Certainly this article does. It's
an interesting effect.

~~~
noobiemcfoob
In my experience and implied by the rising popularity of python, you would be
among the minority. Personally, I find python to be the most clear of any
language I've worked with, most resembling natural language in the way I
typically speak. Do you have some examples of how you find it self-
contradictory?

Here's an example of its expressiveness a colleague and mine I discussing the
other day: Python: [os.remove(i.local_path) for i in old_q if i not in
self.queue] Java: old_q.stream().filter(i -> !self.queue.contains(i)).map(i ->
new Path(i.local_path)).forEach(Files::delete);

I've programmed in both languages but joked I could only understand the Java
line by using the Python line as documentation!

~~~
Singletoned
I've always found the fork bomb page on Wikipedia to be a good example of
different languages.
[https://en.wikipedia.org/wiki/Fork_bomb](https://en.wikipedia.org/wiki/Fork_bomb)

The Python is example is both short and obvious, whereas the other examples
tend to be either cryptic or complicated.

~~~
dr_zoidberg
I'd slightly change the wikipedia article to use "while True:" instead of
"while 1:". Also, the C# is also quite readable (though very verbose).

------
commenter23
The point the article makes on comparing floating point values and the
floating point type is true, but it's not because of any rounding error.

It's because the comparison operators are defined for every value. That is,
"True < []" is valid in Python 2.7, along with any other 2 values, regardless
of type. This is a surprising instance of weak typing in Python, which is
otherwise strongly typed, which is why this was fixed in Python 3
([https://docs.python.org/3.0/whatsnew/3.0.html#ordering-
compa...](https://docs.python.org/3.0/whatsnew/3.0.html#ordering-
comparisons)).

This is also not a case of Python doing something useful, like with '"foo"*2'.
The result of the comparison is defined, but it's not useful. I suppose it was
useful for making sure that you can always sort a list, but there are better
ways to do that.

~~~
viraptor
> The point the article makes on comparing floating point values and the
> floating point type is true, but it's not because of any rounding error.

Do you mean this example? (it's the only one I can find about floating point
comparison)

> 2.2 * 3.0 == 3.3 * 2.0

It's definitely due to accuracy error. (rather than type comparison) How would
you explain it otherwise?

~~~
masklinn
Accuracy problems in floating-point computations:
[https://en.wikipedia.org/wiki/Floating_point#Accuracy_proble...](https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems)

~~~
viraptor
My bad, wrote rounding not accuracy. (corrected now) But my point was that
it's not related to weak typing as the parent seems to suggest.

~~~
masklinn
> But my point was that it's not related to weak typing as the parent seems to
> suggest.

And you're completely right there. Any language using floating-point numbers
will have the same issue regardless of its typing discipline e.g. Rust:
[https://is.gd/4BNoWa](https://is.gd/4BNoWa)

------
Pikago
The documentation of most modules cited in the article start with a paragraph
in red and bold warning the reader of the same danger explained by the author.
So this is a nice compilation, but nothing new and nothing somebody looking at
the documention of the module he's using will miss.

There are nonetheless good remarks about poor design choices of Python which
can lead to misconceptions to newbies, such as naming `input` the function
that does `eval(raw_input(prompt))` (as casually documented[0]), and the
existence of such function in a first place.

[0]
[https://docs.python.org/2/library/functions.html?highlight=i...](https://docs.python.org/2/library/functions.html?highlight=input#input)

------
santiagobasulto
Completely out of context, sorry, but couldn't avoid to note this:

"Being easy to pick up and progress quickly towards developing larger and more
complicated applications, Python is becoming increasingly ubiquitous in
computing environments".

Why would you change the order of the subject in such an unreadable way? Isn't
much easier to say:

"Python is becoming increasingly ubiquitous in computing environments, as it's
easy to pick up and progress quickly towards developing larger and more
complicated applications"

I'm not expert in writing, it just sounded weird. If anyone can explains
what's going on there, really appreciated.

~~~
bbctol
Sounds to me like they started with something like "Being easy to pick up,
Python is becoming increasingly ubiquitous..." and then made the old engineer-
as-writer error of adding extra specificity.

------
amyjess
On the float behavior: I really wish Python 3 had the sense to do what Perl 6
did and interpret all literals with decimal points (except those that use
scientific notation) as Fractions instead of floats. That would solve all
these floating-point errors without requiring significant modification of
code, plus Python 3 would be the perfect time to do it because they're already
throwing out backwards compatibility because of the str/bytes thing.

~~~
tomato123
That would kill Python for scientific use. Also, while nice, Fractions have
their own pitfalls due to potentially catastrophic runtime behavior.

~~~
amyjess
> That would kill Python for scientific use.

If Python used the Perl 6 model, you could still use floats by writing your
literals in scientific notation, so if you want floating-point performance,
you can still get it. For example, 2.2 would be a Fraction, but 2.2e0 would be
a float. I don't want to eliminate floats from the language, just hide them
from average users by default.

And it's not like rationals-as-default are just some weird Perl 6-ism. Haskell
does the same thing, and the language is fairly well-received.

> Also, while nice, Fractions have their own pitfalls due to potentially
> catastrophic runtime behavior.

Elaborate?

------
mrswag
Some points are valid, but come on, if an attacker has write access to your
code, you can't recover from that, ever.

~~~
viraptor
> but come on, if an attacker has write access to your code

Why is this relevant for this article? The article doesn't say anything about
attackers having write access to the source.

~~~
mrswag
It's in their threat model under 'Module injection':

> The mitigation is to maintain secure access permissions on all directories
> and package files in search path to ensure unprivileged users do not have
> write access to them.

~~~
viraptor
Ok, I see. To be honest I read that as "keep your PYTHONPATH sane". I think
that's a bit different from worrying about someone having write access to the
source, but still related - point taken.

~~~
zwp
CVE-2008-5983
([https://bugs.python.org/issue5753](https://bugs.python.org/issue5753))
"Untrusted search path vuln... prepends an empty string to sys.path when the
argv[0] argument does not contain a path separator"

Check out the "yes"es in the "fixed" column in comment at
[https://bugs.python.org/msg85966](https://bugs.python.org/msg85966)

------
guyzmo
The part of the article about an issue with name mangling of private fields is
somehow misleading.

The feature is just some syntactic sugar.

When within a class, private fields such as:

    
    
        class Foo:
            def __init__(self):
                self.__bar
    

are accessible from within other methods of class Foo as `self.__bar`. But
that's just syntactic sugar, the real name of `self.__bar` is
`self._Foo__bar`.

So from the outside "world", including `hasattr()`, you can still access
`self.__bar` as `Foo()._Foo__bar`.

    
    
        >>> class Foo():
        ...   def __init__(self):
        ...     self.__bar = 'hello'
        ...   def show(self):
        ...     print(1, self.__bar)
        ...     print(2, getattr(self, '__bar'))
        ... 
        >>> foo = Foo()
        >>> foo._Foo__bar
        True
        >>> foo.show()
        1 hello
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 6, in show
        AttributeError: 'Foo' object has no attribute '__bar'
        >>> foo.__bar = 'world'
        >>> foo.show()
        1 hello
        2 world
    

In the end, when `x.__private` is setup outside of the class definition,
obviously, it's a new member as its name differs from the internal name
`__private` (which really is `_X__private`).

From within the code doing `getattr('X', '__private')` will return the
`__private` setup from outside the class, and `getattr('X', '_X__private')`
the one defined from within the class.

The whole point of that feature is to ensure that members defined within a
class that are not part of the public API are left untouched when that class
get subclassed, to avoid unexpected behaviours.

Here's an example of why this has been designed:

    
    
      >>> class A:
      ...   def __init__(self):
      ...     self.__internal = "this is a"
      ...   def show(self):
      ...     print(1, "A", self.__internal)
      ... 
      >>> class B(A):
      ...   def __init__(self):
      ...     super(B, self).__init__()
      ...     self.__internal = "this is b"
      ...   def show(self):
      ...     super(B, self).show()
      ...     print(2, "B", self._A__internal)
      ...     print(3, "B", self.__internal)
      ... 
      >>> B().show()
      1 A this is a
      2 B this is a
      3 B this is b
      >>> 
    

There's nothing that should be surprising or asymmetrical to anybody who've
read the python documentation, and use that feature appropriately. It's maybe
a weird feature, but it's still a coherent and homogeneous behaviour and
actually adding more safety to codes.

Documentation references:

    
    
      * https://docs.python.org/3/faq/programming.html#i-try-to-use-spam-and-i-get-an-error-about-someclassname-spam
      * https://docs.python.org/3/reference/expressions.html#atom-identifiers

~~~
mkesper
"Private" fields and methods should use one underscore. Two underscores are
for name mangling issues and __method__ is for "magic" methods.

~~~
zardeh
I'd say that private methods are double underscored, and protected methods are
single underscored, since the goal of the __ is to prevent child classes from
being able to use the parent implementation via self.__meth.

~~~
xapata
Not prevented from being able to use, but from _accidentally_ using or
overriding the parent's. The child can still use the mangled name.

------
aikah
the last one (script injection) isn't limited to python but any language that
make use of template engine. escaping variables should be the default
behavior.

Now I like python, it has many useful libraries, in fact it is one of the
language that has the most libraries for any purpose. I wish, even as a
dynamically typed language, it was stricter sometimes though.

------
wallunit
I'm sorry but that whole article is just FUD...

> Input function

Yes, in Python 2, input() is a shortcut for eval(raw_input(...)), and
documented as such. Obviously that is not a safe way to parse user input, and
therefore it has been changed in Python 3. So this has been fixed, but if you
don't read the documentation you probably will keep introducing security
issues with whatever programming language.

> Assert statement

If you want to effectively protect against a certain condition, raise an
exception! Asserts, on the other hand, exist to help debugging (and
documenting) conditions that should never occur by proper API usage. Stripping
debugging code when optimizing is common practice, not only with Python.

> Reusable integers

First of all, this behavior isn't part of the Python programming language, but
an implementation detail, and a feature as it reduces memory footprint. But
even when small integers wouldn't be cached, you would still have the same
situation when using the is operator on variables holding the same int object.
On the other hand, caching all integers could easily cause a notable memory
leak, in particular considering that ints in Python 3 (like longs in Python 2)
can be as large as memory available. But either way, there is no good reason
to check for identify if you want to compare values, anyway.

> Floats comparison

floats in Python use essentially the native "double" type. Hence they have
whatever precision, your CPU has for double precision floating point numbers,
actually it is specified in IEEE 754. That way floating point numbers are
reasonable fast, while as precise as in most other programming languages.
However, if that still isn't enough for your use case, Python also comes with
the decimal module (for fixed-point decimal numbers) and the fractions module
(for infinite precision fractions).

And as for infinity, while one would expect float('infinity') to be larger
than any numerical value, the result of comparing a numerical value with a
non-numerical type is undefined. However, Python 3 is more strict and raises a
TypeError.

> Private attributes

Class-private attributes (those starting with __) exist to avoid conflicts
with class-private attributes of other classes in the class hierarchy, or
similar accidents. From my experience that is a feature that is rarely needed,
even more rarely in combination with getattr()/setattr()/delattr(). But if you
need to dynamically lookup class-private attributes you can still do so like
hastattr('_classname__attrname'). After all, self.__attrname is just
syntactical sugar for self._classname__attrname.

Also note that private attributes aren't meant as a security mechanism, but
merely to avoid accidents. That's not specific to Python; in most object-
oriented languages it is possible to to access private attributes, one way or
another. However, Python tries to be transparent about that fact, by keeping
it simple.

> Module injection

Yes, Python looks in a few places for modules to be imported. That mechanism
is quite useful for a couple of reasons, but most notably it's necessary to
use modules without installing them system-wide. It can only become a security
hole if a malicious user has write access to any location in sys.path, but not
to the script, importing the modules, itself. I can hardly think about a
scenario like that, and even then I'd rather blame the misconfiguration of the
server.

> Code execution on import

Yes, just like every other script language, Python modules can execute
arbitrary code on import. That is quite expected, necessary, and not limited
to Python. Even if module injection is an issue, it doesn't make anything
worse, as you you don't necessarily have to run malicious code on module
import but could do it with whatever API is being called. But as outlined
above, this is a rather theoretical scenario.

> Shell injection via subprocess

Yes, executing untrusted input, is insecure. That is why the functions in
Python's subprocess module, by default, expect a sequence of arguments, rather
than a string that is parsed by the system's shell. The documentation clearly
explains the consequences of using shell=True. So introducing a shell
injection vulnerability by accident, in Python, seems less likely than with
most other programming languages.

> Temporary files

If anything, Python is as unsecure as the underlying system, and therefore as
most other programming languages too. But CWE-377, the issue the author is
talking about, isn't particular easy to exploit in a meaningful way, plus it
requires the attacker to already have access to the local temporary directory.
Moreover, Python's tempfile module encourages the use of high-level APIs that
aren't effected.

> Templating engines

The reason jinja2 doesn't escape HTML markup by default is that it is not an
HTML template engine, but a general purpose template engine, which is meant to
generate any text-based format. Of course, it is highly recommended to turn on
autoescaping when generating HTML/XML output. But enforcing autoescaping would
break other formats.

------
tehwalrus
Having written code in Python for a few years, I've come across most of these
(some of the ways to hack builtins/modify the code on a function reference
were new to me).

However, it had also never occurred to me to make anything I cared about the
security of in python. Perhaps this article is aimed at people who are writing
system utilities for linux distributions, and are considering Python?
Presumably some such utilities are written that way already.

It comes down to doing a proper security analysis before you define the
requirements of the software: Specifically what attack vectors you want to
defend against. A valid conclusion for some types of software, given the list
of "bugs" in the post, would be _don 't write it in Python._ (Indeed, I have
done exactly this before writing 200 lines of C instead of 20 lines of
Python.)

~~~
viraptor
> A valid conclusion for some types of software, given the list of "bugs" in
> the post, would be don't write it in Python.

Do you have some specific types in mind? I know some types of protection are
not reachable from python directly and require native modules, but I'm not
sure what would cause you to drop Python altogether. I'd be interested to hear
some examples.

~~~
tehwalrus
Well, for example, you might want to write a package manager for a Linux
distribution[1]. An attacker could change the behaviour of someone's package
manager by some non-privileged means (messing with the user's python path and
placing an evil self-hiding module that changes the behaviour of the engine
e.g. by always listing a malicious package as a dependency of whatever you're
currently installing).

The problematic code here is Python's `import` mechanism and mutable global
references to standard library functions. You can cut out the "buggy" code by
writing in another language.

[1] [https://wiki.gentoo.org/wiki/Python](https://wiki.gentoo.org/wiki/Python)

~~~
viraptor
That's possible, but I don't think it's relevant. If you have access to the
user's profile, it doesn't really matter what language the package manager
uses. You still have rights to create aliases, create local wrapper scripts,
use LD_PRELOAD, set LD_LIBRARY_PATH, and many other ways to execute your own
code. You can stop the user accessing the original package manager in the
first place.

dnf for example is widely used (Fedora's default package manager) and it's
written in Python: [https://github.com/rpm-software-
management/dnf](https://github.com/rpm-software-management/dnf)

------
sitkack
So many people chiming in with their dismissive comments and superior Python
knowledge. The article is excellent and should be required reading for Python
devs. Having it in one place is valuable resource.

------
swampthinker
I love that the header and navbar is responsive, but the content itself is
not.

Also input is truly baffling to me. Such a small mistake that could allow
write access to your code.

~~~
ekimekim
it's historical - python was originally conceived as a toy language for
teaching, in which context being able to do x = input(), type 2, and get an
integer, is a desirable property.

Then we got stuck with it because backwards compatibility.

------
dschiptsov
"Reusable integers" is a real fail - it violates the principle of least
surprise and introduces a nasty inconsistency - _all integers_ should
logically be (refer to) the same integer object, not just the first 100.

Assert is a statement, not an expression, so do not use it as an expression.

One should never compare floats. This is taught in any freshman CS course. The
limitation is due to the standard encoding of floats - IEEE 754 - not Python's
fault.

Everything else are features of a truly dynamic language, designed for a
_really_ quick prototyping. Python3.x got rid of many inconsistencies and
caveats of 2.x

Shall we re-read the classic now?

[https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-
design/](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/)

~~~
in_the_sticks
If your code relies on two integers having the same object ID, I daresay you
may be doing something wrong.

~~~
dschiptsov
Yes, but it ether should be that way for all or for none of them, not for some
of them.)

Logically, they should refer to the same entity. It is "natural" \- when
people are trying to communicate a concept to one another they assume they are
referring to _the same concept_. Not to an instance of it.)

------
ViktorasM
Not readable on a phone. How can any tech company afford this in 2016...

