March 2017

Architectural Flaws: The Enemy Of Software Security

“Microsoft reports that more than 50% of the problems the company uncovered during its ongoing security push are architectural in nature. Cigital data shows a 60/40 split in favor of architectural flaws.”
- Gary McGraw

Nearly 40% of the 1,000 CWEs (common weakness enumeration) are architectural flaws. Architectural design in secure software is an often overlooked aspect of software development. So much so that the IEEE established a Center for Secure Design and released a document “Avoiding the Top 10 Software Security Design Flaws”.

Static analysis is not enough

The static analysis testing of software source code is necessary but not enough. Architectural flaws are difficult to find via static analysis. Architectural flaws can obscure coding bugs that static analysis might have otherwise detected because of the added complexity. Research from Rich Kazman at the Software Engineering Institute shows that you should focus on identifying design weaknesses to alleviate software bug volume. In identifying structures in the design and codebase that have a high likelihood of containing bugs, hidden dependencies, and structural design flaws, SEI has found that architectural flaws and security bugs are highly correlated (.9 correlation). This is because defective files seldom exist alone in large-scale software systems. They are usually architecturally connected, and their architectural structures exhibit significant design flaws that can propagate bugs among many files.

Example HeartBleed

In his essay “How to Prevent the next HeartBleed” David Wheeler said “OpenSSL uses unnecessarily complex structures, which makes it harder for both humans and machines to review.” There should be a continuous effort to simplify the code. Otherwise, just adding capabilities will slowly increase software complexity. The code should be refactored over time to make it simple and clear while new features are being added. The goal should be code that is “obviously right,” as opposed to code that is so complicated that “I can’t see any problems.”

As we stated above, this is a good example of static analysis techniques not being enough. These techniques that were supposed to find HeartBleed-like defects in OpenSSL were thwarted because the code was too complex. Code that is security-sensitive needs to be “as simple as possible.” Many security experts believe using tools, like Lattix Architect, to detect especially complicated structures and then simplifying those structures is likely to produce more secure software. Simplifying code is a mindset. There needs to be a continuous effort to simplify (refactor) the code. If not, architectural erosion starts to happen as you add capabilities and slowly increase software complexity.

As David stated above, the goal should be code that is obviously right, as opposed to code that is so complicated that you can’t see any errors. I think Rus Cox said it best when talking about HeartBleed and complexity: “Try not to write clever code. Try to write well-organized code. Inevitably, you will write clever, poorly-organized code. If someone comes along asking questions about it, use it as a sign that perhaps the code is probably too clever or not well enough organized. Rewrite it to be simpler and easier to understand.”

Three Ways to Overcome a Big Ball of Mud Software Architecture

Most of the time when people think of high-level software architecture, they think of patterns like Microservices (see our previous blog post What is a Microservices Architecture?"). But the most common software architecture we see when talking to prospects and customers is the Big Ball of Mud architecture. In this post we will define the Big Ball of Mud architecture, discuss why it happens in development, and explore three way of dealing with it (hint: one is refactoring).

What is a "Big Ball of Mud"?

The term was made popular by Brian Foote and Joseph Yoder in their 1997 paper of the same name ("Big Ball of Mud"). They define a Big Ball of Mud as a casually, even haphazardly structured system that is sloppy and sprawling. The code is overly complex and tangled, and is sometimes referred to as spaghetti code. It's generally incomprehensible and lacks any perceivable architecture.

How does it come about?

We are not here to judge anyone. We've all written code we are not proud of or code we cringe at when reading later. Software economics often conspire against us, especially early in the software development process. Time and money are in short supply so we ignore software architecture and other software best practices. Benefits of a high-level architecture are typically realized late in the SDLC when maintainability and modularity become more important. The goal of the typical software project is to deliver quality software on time and under budget. Therefore we focus first on features and functionality, then focus on architecture and performance. As Kent Beck said "Make it work, make it right, make it fast". This is how we end up with a Big Ball of Mud. The structure may have never been defined or the architecture has eroded beyond recognition because of unregulated growth and repeated "quick" fixes. Maybe the project started with some throwaway code that was never intended for production use. Maybe the code was continually added to and fixed but never redesigned. "Just keep it working at all costs; we are too busy to worry about software architecture and other niceties" is the typically refrain. Big Balls of Mud are also created when a well-defined architecture erodes. The architecture works well for the requirements it was written against but when those requirements change this can, and frequently does, undermine the entire structure.

Three ways to overcome the Big Ball of Mud

Facing a Big Ball of Mud can be a daunting challenge. From our experience there are three ways to deal with it:

A. Wallow in the muck (all hail the all powerful Big Ball of Mud)

Some developers enjoy working with overly complex code and hard to use systems. If you are one of those developers, you might like The International Obfuscated C Code Contest (IOCCC). This is a computer programming contest for the most creatively obfuscated C code. According to the website, they will be accepting entries in mid-2017 for the 25th IOCCC.

B. Throw away your source code and start over again

This can be a hard pill to swallow, but if a software system's complexity is allowed to increase unchecked it will eventually reach a point where developers can no longer cope with it. Once simple repairs become all day affairs, management can no longer forecast fixes or features. It is time to consider drastic measures, but this should be the option of last resort.

C. Refactor, repair, and enhance the software system's architecture

The mortal enemy of mud is sunshine, so one of the most effective ways to overcome a Big Ball of Mud is to visualize it. The easiest way to visualize a large complex software system is with a Dependency Structure Matrix.
Dependency Structure Matrix
See our video on using a DSM with a software system: How to Read a Dependency Structure Matrix and Apply Partitioning".

A good visual representation of the software sets the stage for refactoring, repair, and rehabilitation. The first step on the road to a better architecture is to identify the disordered parts of the system and to get a handle on them. Find those sections of the code that seem less tightly coupled and start drawing architectural boundaries there. Then you can perform what-if analysis. What if I add or delete an abstraction? What if I remove the dependency between these two sections? For more information on how we tackle refactoring check out our Refactoring page. To counteract the Big Ball of Mud, a permanent commitment to consolidation and refactoring must be made. Refactoring is the primary means by which programmers maintain order in the software system. The goal of refactoring should be to leave the system working as well after a refactoring as it was before refactoring.

During software development it is perfectly natural to write messy/sloppy code, but just as your mom used to nag you to clean up your room, you should always clean up after yourself when you write software (refactor). A sustain commitment to refactoring can keep a software system from becoming a Big Ball of Mud. Also, where possible, architectural erosion should be prevented, arrested or even reversed. The project is that opportunities and insights into architecture typically happen only later in the software development process so Big Balls of Mud can be quite common.

Finally, the quality of one's tools can influence a software system's architecture. If a software system's architecture goals are inadequately communicated and visualized among team members, they are harder to take into account as the software is developed. See how Lattix Web makes communication easy in the Lattix Web demo site.

What is a Microservices Architecture?

“In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a base minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.”
- James Lewis and Martin Fowler

From this definition, we understand that microservices are small, independently deployable services that work together. These services are focused on doing one thing well (Single Responsibility Principle). In this style, you break down your larger system into multiple microservices that will interact with each other to accomplish the larger goal. There is no standard model for a microservices architecture but most share some notable properties.

microservices architecture

First, they are autonomous. Microservices are typically created by componentizing the software, a component being a unit of software that is independently replaceable and upgradeable. This is especially important as more applications are being deployed to the cloud where load demands can increase dramatically. All communication happens via lightweight networking calls (APIs). The aim is to be as decoupled and as cohesive as possible.

Microservices do not need a standard technology stack. While traditionally large software applications standardize on a single technology stack, when you split your software into independent services you can choose your technology stack for each service. For example, you can use C/C++ for real-time services, Java for the GUI, and Node.js for reporting. You should remember that there will be overhead for having different technology stacks for each service.

You need to design your services to handle the failure of other services; they need to be resilient. This is a side-effect of making the software individual components. You need to consider how a failure of a single service will affect the overall user experience. As a consequence of this, each service should fail as quickly as possible and restore itself automatically, if possible.

Scaling is one of the big advantages of microservices and one of the reasons it is so popular. Since each service (feature) does not depend on the other services, they can be deployed separately. You can now distribute the services across servers and replicate them as load demands increase. Compare this to traditional development that must scale the entire application as demands increase.

Different microservices can be owned by different teams. Teams are cross-functional, which means that they contain the full range of skills for the development of the service. The development team builds the software and owns the product for its lifetime. An example of this is Amazon’s philosophy "You build it, you run it." The advantage is that the development team now has special insight into how users are using their service and can tailor future development to their needs. Teams can also be organized around business capabilities but you will need to watch out for Conway’s Law where the design tends to mimic the organizational structure (see our blog post Overcoming Conway’s Law: Protecting Design from Organization).

Finally, because of the componentization of the software there is a lot of opportunity for reuse of functionality. In the microservices architecture, as you are making the software individual components you should also be designing them so that many different programs can reuse the functionality. This does take significant effort and awareness to be done correctly but can also lead to increases in quality and productivity (for more information see “Effects of Reuse on Quality, Productivity and Economics" by Wayne C. Lim).

To summarize, microservices: are autonomous, using services to componentize the software; use decentralized governance; are designed to handle service interruptions; are easily scalable; focus on products not projects; are typically organized around business capabilities; and promote software reuse. To learn more see our whitepaper "Developing a Microservices Architecture."