Building a Truly Secure System

Tangram Flex
theFramework
Published in
6 min readFeb 26, 2020

This is the third post in a series on secure software methodologies from Tangram Flex.

This week’s series is all about secure software. In The Theory of Secure Software we introduced the challenge: engineers need to verify that the software they develop is “guaranteed” to do what it’s supposed to do. We love a good theory, and at Tangram Flex we’re taking a lot of really good ideas and putting them into practice. Yesterday’s post, Correct by Construction, described our preferred method of building secure, correct software. It all starts with a modular architecture. This model is our contract: the final source of truth against which we can prove that the final software component will produce the guaranteed output as long as it receives the assumed input.

Software developers take on the next three steps in the correct by construction methodology:

2. Implement functionally correct code

3. Harden interfaces between components

4. Implement system-level security

It’s Got Good Bones

A software developer’s first job in building secure software systems is to implement functionally correct code. This means software components should only contain ‘good’ code- free from bugs, faults, errors, warnings, aliens, viruses, and atomic weapons. In a perfect world, software developers would write immaculate code and the software components that already exist would be known to be flawless. In the ACTUAL world, we know this isn’t the case. Many attacks take advantage of software flaws, using security holes to cause a system to deviate from its expected behavior.

Functional correctness means that software behaves exactly as intended… and in no other way. Removing or minimizing ‘bad’ code is the most reliable way to increase security and ensure good behavior. Even further, removing unused code makes it harder for an attacker to change the software’s behavior.

When a software component does not pass testing, the developer should first try to fix the code to correct the behavior. Sometimes this isn’t feasible- especially when code from an external source is being used. Forking and modifying third party software can create a huge maintenance burden with security implications that are difficult to predict.

If the code can’t be fixed, a modification to the system model may be required. The system model is a contract shared between everyone working on the system. Grading code against the model reduces the risk that any component is developed in a vacuum with inaccurate information about how it relates to other pieces of the system. The software developer needs to update the assumptions in the model to reflect the inputs that would enable the component to provide the needed function.

A huge range of component assurance techniques is available today- but no single silver bullet exists. A holistic approach works best. Combining methods from code review all the way up to formal methods, with static analysis and dynamic code execution in between, is a solid approach. An appropriate testing and analysis approach should provide confidence that software satisfies the functional requirements of the component model. The model is a living source of truth that ensures every team member operates under the same set of assumptions and guarantees.

Putting Up Walls

Functionally correct code is necessary for a secure system, but it doesn’t stand alone. Sometimes flawed code cannot be updated. Attackers are both creative and persistent, and very often will find a way to get into a component with a vulnerability. Hardening the interfaces between components is a proven method for limiting what an attacker can access within a system. The goal is to contain the spread of damage to a single component.

You can think of component interfaces like the walls of a house. When guests are coming over, you can close the doors to messy rooms. Guests can enjoy dinner in your dining room without being overcome with the odor of the gym bag closed up in your laundry room.

There are many types of interface hardening, ranging from simple filtering to fully certified cross-domain solutions. Again, a holistic solution catered to the system’s unique model is the best approach to securing interfaces. There are two keys to hardening an interface:

  1. Limit interface functionality. Code that doesn’t exist can’t be compromised in an attack.
  2. Identify and monitor key interfaces to ensure assumptions are met and inputs that cause bad behavior are suppressed.

The goal is to create a ‘white listed’ interface that rejects unknown messages by default, resulting in security as a default setting.

Interface hardening requires balance and tradeoffs, especially when existing and off-the-shelf software is used in a system. In some cases, interfaces are executed through third-party libraries which may not be fully evaluated by the component developer. Unused code in an application may be vital when the component is re-purposed, making its removal impractical. Hardening interfaces requires tough decisions, but it’s worth it.

Installing a security system

Even if every software component has perfect code and interfaces are secure as can be, it is essential to implement system-level security. Even the sturdiest house is much more secure when it’s surrounded by a gate and has an alarm system.

Since systems typically don’t exist in a vacuum, even correct system behavior can be used maliciously by humans or other systems. For example, there are perfectly valid requirements for a drone pilot to intentionally fly a drone into the ground — imagine the engine fails and the pilot finds a safe place to crash. This means there must be valid code logic in the flight controller to allow this behavior. This same valid logic can be taken advantage of by someone pretending to be the pilot.

Having the pilot provide a method of authentication to the system reduces the risk that an attacker will easily be able to gain access to maliciously enter flight termination mode. This is a system-level security attribute that would be very hard to design at the component or interface level. It would be very difficult to build a flight controller with the ability to recognize a user.

System-level user authentication does not protect against flaws in the flight controller code. Without hardened interfaces, flawed code might allow a message from a compromised system component to cause the flight controller to enter flight termination mode. A truly secure system requires user authentication, hardened interfaces to ensure that the command could only be passed properly from the correct component, and a functionally correct flight controller that could not unintentionally enter flight termination mode.

Security Is a Practice

Correct by construction methodology has a fifth step: evaluate and iterate. This is essential for modern systems. All code provided by software developers will be graded against the model over and over again each time the system, its components, or the model change. Building secure systems requires adjustments to the way we model, code, develop, implement, and test software.

The final post in our series talks about the state of secure software today and what’s next. Follow us on Twitter and LinkedIn for the latest.

Adapted from a paper written by Don Barrett, Systems Engineer at Tangram Flex

Edited by Liz Grauel, Technical Writer at Tangram Flex

--

--

Tangram Flex
theFramework

Bringing engineering expertise and product solutions together to modernize the world’s most crucial systems faster and with greater confidence. tangramflex.com