- The Art of Defensive Programming
- Diego Mariani
- The Nuggets translation Project
- Translator: GiggleAll
- Proofread by: tanglie1993, FGHPDF
Why don’t developers write secure code? We’re not talking about “clean code” here. Let’s talk more about software security from a pure point of view. Yes, because an insecure software is almost useless. Let’s take a look at what insecure software means.
-
The European Space Agency’s Ariane 5 Flight 501 was destroyed 40 seconds after takeoff (4 June 1996). The $1 billion prototype rocket self-destructed due to an error in the onboard navigation software.
-
In the 1980s, an error in the code used to control therAC-25 radiation in a treatment machine led to the overdose of X-rays that killed at least five patients.
-
A software error in the Mim-104 Patriot caused its system clock to shift by a third of a second over a 100-hour period, making it impossible to locate and intercept incoming missiles. Iraqi missiles attacked a Saudi Military compound in Dahlan (25 February 1991), killing 28 Americans.
These examples are enough to remind us how important it is to write secure software, especially under certain circumstances. In other use cases, we should also know what our software bugs can do to us.
Defensive programming Angle one
Why do I think defensive programming is a good way to find these issues on some projects?
Defense is impossible, because the impossible will be possible.
There are many definitions for defensive programming, and it also depends on the level of security and the level of resources required by your software project.
Defensive programming is a defensive design designed to ensure the continuity of software functionality in unexpected situations. Defensive programming practices are often used in highly available, secure places – Wikipedia
I personally think this approach works well when you’re dealing with a large, long-term project with many people involved. For example, open source projects that require a lot of maintenance.
In order to implement a defensive approach to programming, let me share my humble opinion.
Suppose you always get something you don’t expect. This should be your approach as a defensive programmer, for user input, or whatever else comes into your system normally. Because we can expect the unexpected and try to be as strict as possible. Assert that your input values are what you expect.
Offense is the best defense
Whitelisting rather than blacklisting the input, for example, when validating an image extension, instead of checking for invalid types, check for valid types and exclude all the rest. In PHP, there are countless open source validation libraries to make your job easier.
Offense is the best defense, with strict control.
Using data abstraction
The first of OWASP’s top 10 security vulnerabilities is injection. This means that there are people (many people) who haven’t used security tools to query their databases. Use database abstraction packages and libraries. In PHP you can use PDO to ensure basic injection protection.
You don’t use frames (or microframes)? You just love doing extra work for no reason. Congratulations to you! As long as it’s well-tested, well-trusted, stable code, you can use it for all kinds of new features (not just frameworks) instead of reinventing the wheel just because it’s already built. The only reason you build your own wheel is because you need something that doesn’t exist or exists but doesn’t fit your needs (poor performance, missing features, etc.).
That (using frameworks) is what we call intelligent code reuse, and it’s worth having.
Don’t trust developers
Defensive programming can be associated with something called defensive driving. In defensive driving, we assume that everyone around us is likely to make a mistake. So we have to be careful what other people do. The same applies to our defensive programming, and as developers, we shouldn’t trust other developers. We shouldn’t trust our code either.
In large projects with many people involved, there are many different ways to write and organize code. This can also lead to confusion and even more mistakes. That’s why our uniform coding style and use of code detectors makes our lives easier.
This is what makes it difficult for a defensive programmer, writing code that doesn’t suck. This is something that a lot of people know and talk about, but no one really cares or puts the right attention and effort into implementing SOLID code.
Let’s look at some bad examples.
Do not: uninitialized attributes
<? php class BankAccount { protected $currency = null; public function setCurrency($currency) { ... } public function payTo(Account $to,$amount) { // sorry for this silly example $this->transaction->process($to,$amount,$this->currency); } } // I forgot to call $bankAccount->setCurrency('GBP'); $bankAccount->payTo($joe,100);
Copy the code
In this case, we must remember that in order to send the payment, we need to call the setCurrency first. This is a very bad thing, and a state change operation like this (issuing a payment) should not use two (or more) public methods in two steps. We can still have many ways to pay, but we must have only one simple public method to change the state (objects should never be in an inconsistent state).
In this case, we could do a better job of encapsulating the uninitialized properties into the Money object.
<? php class BankAccount { public function payTo(Account$to,Money$money){ ... } } $bankAccount->payTo($joe,newMoney(100,newCurrency('GBP')));
Copy the code
Make it foolproof. Do not use uninitialized object attributes.
Do not: Exposed state outside of class scope.
<? php class Message { protected $content; public function setContent($content) { $this->content=$content; } } class Mailer { protected $message; public function__construct(Message$message) { $this->message=$message; } public function sendMessage( { var_dump($this->message); } } $message = new Message(); $message->setContent("bob message"); $joeMailer = new Mailer($message); $message->setContent("joe message"); $bobMailer = new Mailer($message); $joeMailer->sendMessage(); $bobMailer->sendMessage();
Copy the code
In this case, the message is passed by reference, and the result will be “Joe Message” in both cases. The solution is to clone the message object in the Mailer constructor. But we should always try to use an (immutable) value object instead of a simple Message mutable object. Use immutable objects when you can.
<? php class Message { protected $content; public function __construct($content) { $this->content = $content; } } class Mailer { protected $message; public function __construct(Message $message) { $this->message = $message; } public function sendMessage() { var_dump($this->message); } } $joeMailer = new Mailer(new Message("bob message")); $bobMailer = new Mailer(new Message("joe message")); $joeMailer->sendMessage(); $bobMailer->sendMessage();
Copy the code
What else do we need to say? Writing unit tests will help you adhere to common principles such as high aggregation, single responsibility, low coupling, and the right combination of objects. It not only helps you test small units, but also tests the way your objects are structured. In fact, you’ll see exactly how many units you need to test in order to test your little feature and how many objects you need to emulate to achieve 100% code coverage.
conclusion
Hope you enjoyed this article. Remember that these are just suggestions, and it’s up to you when and where to take them.