Can the usage of `setattr` (and `getattr`) be considered as bad practice?
Asked Answered
H

1

12

setattr and getattr kind of got into my style of programing (mainly scientific stuff, my knowledge about python is self taught).

Considering that exec and eval inherit a potential danger since in some cases they might lead to security issues, I was wondering if for setattr the same argument is considered to be valid. (About getattr I found this question which contains some info - although the argument is not very convincing.)

From what I know, setattr can be used without worrying too much, but to be honest I don't trust my python knowledge enough to be sure, and if I'm wrong I'd like to try and get rid of the habit of using setattr.

Hobby answered 20/12, 2012 at 23:47 Comment(5)
It depends, it's not inherently evil, but there are often better ways to solve the problem at hand.Iolenta
@Lattyware better in which sense? more secure, shorter, more concise? could you be more precise?Hobby
Are you worried about "danger" as in "exploitable security risk", or "less readable code"? I'm pretty sure Lattyware means "more readable, more maintainable, more idiomatic", rather than shorter or more concise, because almost nobody cares, or should care, nearly as much about the latter. ("Secure" is a whole different question—people do, and should, care about that. That's why I asked for clarification.)Righteous
@Righteous the readability does not really matter (at least not for this precise question). It is concerning the security risk.Hobby
I don't think the linked question about getattr is really relevant—everyone there is talking about performance, not security.Righteous
R
14

First, it could definitely make it easier to an existing security hole.

For example, let's say you have code that does exec, eval, SQL queries or URLs built via string formatting, etc. And let's say you're passing, say, locals() or a filtered __dict__ to the formatting command or as the eval context or whatever. Using setattr clearly widens the security hole, making it much easier for me to find ways to attack your code, because you can no longer be sure what you're going to be passing to those functions.

But what if you don't do anything else unsafe? Is setattr safe then?

Not as bad, but it's still not safe. If I can influence the names of the attributes you're setting, I can, e.g., replace any method I want on your objects.

You can try to protect against this by, e.g., first checking that the old value was not callable, or not a method-type descriptor, or whatever. In the same way you can try to protect against people calling functions in eval or putting quotes and semicolons in SQL parameters and so on. This is effectively the same as any of those other cases. And it's a lot harder to try to close all illegitimate paths to an open door, than to just not open the door in the first place.

What if the name never comes from anything that can be influenced by the user?

Well, in that case, why are you using setattr? There is no good reason to call setattr with a literal.

Anyway, when Lattyware said that there are often better ways to solve the problem at hand, he was almost certainly talking about readability, maintainability, and idiomaticness. But the side effect of using those better ways is that you also often avoid any security implications.

90% of the time, the solution is to use a dict instead of an object. Unlike Javascript, they're not the same thing in Python, and they're not meant to be used the same way. A dict doesn't have methods, or inheritance, or built-in special names, so you don't have to worry about any of that. It also has a more convenient syntax, where you can say d['foo'] instead of setattr(o, 'foo'). And it's probably more efficient. And so on. But ultimately, the reason to use a dict is the conceptual reason: a dict is a named collection of values; a class instance is a representation of a model-space object, and those are not the same thing.

So, why does setattr even exist?

It's there for the same basic reasons as other low-level features, like being able to access im_func or func_closure, or having modules like traceback and imp, or treating special methods just like any other methods, or for that matter exec and eval.

First, you can build higher-level things out of these low-level tools. For example, to build collections.namedtuple, you'd need either exec or setattr.

Second, you occasionally need to monkey-patch code at runtime because you can't modify it (or maybe even see it) at compile time, and tools like setattr can be essential to doing that.

The setattr feature—much like eval—is often misused by people coming from Javascript, Tcl, or a few other languages. But as long as it can be used for good, you don't want to take it out of the language. (TOOWTDI shouldn't be taken so literally that only one program can ever be written.)

But that doesn't mean you should go around using this stuff whenever possible. You wouldn't write mylist.__getitem__(slice(1, 10, 2)) instead of mylist[1:10:2]. Sometimes, being able to call __getitem__ directly or build slice objects explicitly is a foundation to something that lets the rest of your code be more pythonic, or way to localize a workaround to avoid infecting the rest of your code. Otherwise, there are clearer and simpler ways to do it.

Righteous answered 21/12, 2012 at 0:5 Comment(6)
I can replace any method I want on your objects. — Can't you do that anyway?Misteach
@detly: Sure, if I just want to run a local copy of your code. But who cares about that? Presumably the reason I want to attack your code through data is that it's a web service, root daemon, etc. So I want to get you to eval my expression, or inject my SQL into your SQL statement, or replace your method, or whatever, so the copy running as root/on the web/whatever does what I want, not just my useless local copy.Righteous
Thanks for these - even for me - clear explanations. Your argumentation seems very reasonable to me, especially also because I do actually not have a particular reason why I'm using setattr. I just found it in some situations convenient, but this should not be a reason. Yet, if there is always a (saver) way, why exists setattr in the first place? (ps. do not have enough points to vote you up :( )Hobby
@TimmClarsson: There are some good uses of setattr. I'll update the answer to explain more.Righteous
Hopefully the edited version helps. But now that I think about it, I could have answered in one sentence: For the same reason eval exists.Righteous
@Righteous most certainly "For the same reason eval exists." is correct, but I'm happy about the longer answer, I can take much more from this.Hobby

© 2022 - 2024 — McMap. All rights reserved.