This chapter includes:
Now that more and more computers and other devices are hooked up to insecure networks, security has become a very important issue. The word security can have many meanings, but in a computer context, it generally means preventing unauthorized people from making your computer do things that you don't want it to do.
There are vast tracts of security information in books and on the Internet. This chapter provides a very brief introduction to the subject of security, points you toward outside information and resources, and discusses security issues that are unique to Neutrino.
It should be fairly obvious that security is important; you don't want someone to take control of a device and disrupt its normal functioning -- imagine the havoc if someone could stop air traffic control systems or hospital equipment from functioning properly.
The importance of security to an individual machine depends on the context:
Part of securing a machine is identifying the level of risk. By classifying threats into categories, we can break down the issues and see which ones we need to concern ourselves with.
We can break the broad division of security threats, also known as exploits, into categories:
Remote exploits are generally much more serious than local ones, but fortunately, remote exploits are much easier to prevent and are generally less common.
For example, suppose you're running bind (a DNS resolver) on port 53 of a publicly connected computer, and the particular version has a vulnerability whereby an attacker can send a badly formed query that causes bind to open up a shell that runs as root on a different port of the machine. An attacker can use this weakness to connect to and effectively "own" the computer.
This type of exploit is often called a buffer overrun or stack-smashing attack and is described in the article, Smashing the Stack for Fun and Profit by Aleph One (see http://www.insecure.org/stf/smashstack.txt). The simple solution to these problems is to make sure that you know which servers are listening on which ports, and that you're running the latest versions of the software. If a machine is publicly connected, don't run any more services than necessary on it.
Local exploits are much more common and difficult to prevent. Having a local account implies a certain amount of trust and it isn't always easy to imagine just how that trust could be violated. Most local exploits involve some sort of elevation of privilege, such as turning a normal user into the superuser, root.
Many local attacks take advantage of a misconfigured system (e.g. file permissions that are set incorrectly) or a buffer overrun on a binary that's set to run as root (known as a setuid binary). In the embedded world -- where Neutrino is typically used -- local users aren't as much of an issue and, in fact, many systems don't even have a shell shipped with them.
Another way of classifying exploits is by their effect:
As an example, there are very few systems that can't be brought to their knees by a malicious local user although, with such tools as the ksh's ulimit builtin command, you can often minimize these attacks.
Using these divisions, you can look at a system and see which classes of attacks it could potentially be vulnerable to, and take steps to prevent them.
A virus is generally considered to be an infection that runs code on the host (e.g. a Trojan horse). Viruses need an entry point and a host.
The entry points for a virus include:
The hosts for a virus are system-call interfaces that are accessible from the point of entry (an infected program), such as sendmail or an HTTP server. The hosts are platform-specific, so a virus for Linux would in all likelihood terminate the host under Neutrino as soon as it tried to do anything damaging.
The viruses that circulate via email are OS-specific, generally targeted at Windows, and can't harm Neutrino systems, since they simply aren't compatible. Most UNIX-style systems aren't susceptible to viruses since the ability to do (much) damage is limited by the host. We have never heard of a true virus that could infect Neutrino.
In addition, since deployed Neutrino systems are highly customized to their designated application, they often don't contain the software that's open to such attacks (e.g. logins, web browsers, email, Telnet and FTP servers).
Neutrino is a UNIX-style operating system, so almost all of the general UNIX security information (whether generic, Linux, BSD, etc.) applies to Neutrino as well. A quick Internet search for UNIX or Linux security will yield plenty of papers. You'll also find many titles at a bookstore or library.
We don't market Neutrino as being either more or less secure than other operating systems in its class. That is, we don't attempt to gain a security certification such as is required for certain specialized applications. However, we do conduct internal security audits of vulnerable programs to correct potential exploits.
For flexibility and familiarity, Neutrino uses the generic UNIX security model of user accounts and file permissions, which is generally sufficient for all our customers. In the embedded space, it's fairly easy to lock down a system to any degree without compromising operation. The ultrasecure systems that need certifications are generally servers, as opposed to embedded devices.
For more information, see Managing User Accounts, and "File ownership and permissions" in Working with Files.
As the above section notes, Neutrino is potentially vulnerable to most of the same threats that other UNIX-style systems face. In addition, there are also some issues that are unique to Neutrino.
This section includes:
Our basic model of operation relies on message passing between the OS kernel, process manager and other services. There are potential local exploits in that area that wouldn't exist in a system where all drivers live in the same address space as the kernel. Of course, the potential weakness is outweighed by the demonstrated strength of this model, since embedded systems generally aren't overly concerned with local attacks.
For more information about the microkernel design and message passing, see the chapter on the QNX Neutrino microkernel in the System Architecture guide.
Our remote debug agent, pdebug, runs on a target system and communicates with the gdb debugger on the host. The pdebug agent can run as a dedicated server on a port, be spawned from inetd with incoming connections, or be spawned by qconn.
The pdebug agent is generally run as root, so anyone can upload, download, or execute any arbitrary code at root's privilege level. This agent was designed to be run on development systems, not production machines. There's no means of authentication or security, and none is planned for the future. See the section on qconn below.
The qconn daemon is a server that runs on a target system and handles all incoming requests from our IDE. The qconn server spawns pdebug for debugging requests, profiles applications, gathers system information, and so on.
Like pdebug, qconn is inherently insecure and is meant for development systems. Unlike for pdebug, we plan to give it a security model with some form of authentication. This will let you leave qconn on production machines in the field to provide services such as remote upgrades and fault correction.
Qnet is Neutrino's transparent networking protocol. It's described in the Using Qnet for Transparent Distributed Processing chapter in this guide, and in Native Networking (Qnet) in the System Architecture guide.
Qnet displays other Neutrino machines on the network in the filesystem and lets you treat remote systems as extensions of the local machine. It does no authentication beyond getting a user ID from the incoming connection, so be careful when running it on a machine that's accessible to public networks.
To make Qnet more secure, you can use the maproot and mapany options, which map incoming connections (root or anyone, respectively) to a specific user ID. For more information, see npm-qnet.so in the Utilities Reference.
IPsec is a security protocol for the Internet Protocol layer that you can use, for example, to set up a secure tunnel between machines or networks. It consists of these subprotocols:
IPsec has these modes of operation:
|The IPsec support is subject to change as the IPsec protocols develop.|
For more information, see IPSec in the Neutrino Library Reference. To find out how to enable IPSec, see "Device enumeration" in the Controlling How Neutrino Starts chapter in this guide.
Just as a building or vehicle uses specially constructed walls to prevent the spread of fire, so computer systems use firewalls to prevent or limit access to certain applications or systems and to protect systems from malicious attacks.
To create a firewall under Neutrino, you can use a combination of:
Sample IP filtering and NAT configuration files are supplied as /etc/ipf.conf and /etc/ipnat.conf.
|If you're using a Neutrino machine as a gateway to an internal network, you're applying IP filtering and NAT, and you're tunneling traffic from your internal hosts through an IPSec tunnel on the the Neutrino gateway, you'll likely need to enable the TCP/IP (npm-tcpip-v6.so) stack option, pfil_ipsec. This option affects the processing order of IPSec and IP filtering in the TCP/IP stack for outgoing packets. This means that IP filtering and NAT are applied to the outgoing traffic before it's sent on the IPsec tunnel.|
This section includes:
To configure IP Filter and NAT, you need to login as root and set up the configuration files, as described in the sections that follow. Then, use the following commands to start the utilities:
mount -Ttcpip lsm-ipfilter.so ipf -f filter_rule_file ipnat -f nat_rule_file
For more information, see ipf, ipnat, lsm-ipfilter-*.so, and mount in the Utilities Reference.
Before you start the IP Filter, you must set up a configuration file to specify the rules for allowing and restricting access to your systems.
In this configuration file:
The filter processes the rules from top to bottom, appending each one to the others. For example, suppose you set up these rules:
block in all pass in all
When a packet comes in, the filter looks at the first rule, block in all. This rule succeeds, so does the filter look at the second rule? The answer is yes; unlike other packet filters, IP Filtering uses a flag to track whether or not it's going to pass the packet on. Unless the flow is interrupted, the filter goes through the entire rule set, and decides whether to pass or deny the packet, based on the last matching rule.
|Remember that the last matching rule takes precedence.|
The above example isn't very useful in the real world. If you don't want to go through all the rules, but exit on the first match, what do you do? You use the quick keyword, like this:
block in quick all pass in all
So now, the filter looks at the first line, and, if it finds a match, exits. It's as if the second line didn't exist.
You can also filter packets based on the IP address they came from:
block in quick from 10.7.0.0/16 pass in all
These rules block all packets from IP Address 10.7.0.0 with a netmask of /16. IP Filter accepts both forms of netmask, so you can also write this as 255.255.0.0. If the filter doesn't find a match for rule 1, it looks at rule 2. But, if the filter finds a match for rule 1, it stops searching, because you specified the quick keyword.
There's usually a machine, called a router, that bridges the outside world to the inside world and vice versa. You can usually tell a router from a regular system because a router generally has more than one interface. Every packet comes in on an interface, and every packet goes out on an interface.
Use these keywords to identify the interfaces:
where n is the interface number. For example, if you want to block all packets on ppp0, use these rules:
block in quick on ppp0 all pass in all
You can also intermix IP addresses and interfaces. The more criteria the firewall needs to match against, the tighter the firewall becomes.
For example, if you want to receive packets on ppp0 but not from IP address 10.7.0.0/16, you could specify a rule like this:
block in quick on ppp0 from 10.7.0.0./16 to any pass in all
These rules mean that the packets from 10.7.0.0 are blocked only if they come in on interface ppp0; they pass through if they come in on another interface.
All of the above examples show you how to filter incoming packets, but you can also filter outgoing packets by using the keyword out. For example:
pass out quick on ppp0 from 22.214.171.124/24 to any block out quick on ppp0 from any to any
In this example, if a packet comes from 126.96.36.199/32, it's sent out by the first rule. If a packet comes from 188.8.131.52/32, the second rule blocks it.
Another important keyword is log. So far, all the examples have quietly either passed or blocked without letting anyone know it was doing it. If you want to make sure that the firewall is doing its job, you can log what's happening, although you probably don't want to log everything. For example:
block in quick on ppp0 from 192.168.0.0/16 to any block in quick on ppp0 from 172.16.0.0/12 to any block in quick on ppp0 from 184.108.40.206/3 to any block in log quick on ppp0 from 220.127.116.11/24 to any pass in all
These rules make the filter log all packets that get blocked on ppp0 from 18.104.22.168/24.
You can also set bidirectional rules on interfaces:
pass out quick on lo0 pass in quick on lo0
Here are some other keywords that you can use in the rules:
block in log quick on en0 proto tcp from any to 22.214.171.124/24 port=23
These are just a few of the combinations of rules and keywords that you can use. For an example of ipf.conf, see "Configuration files for setting up a firewall" in the Examples appendix in this guide.
Before starting NAT, you must create a configuration file that specifies a simple rule, such as:
Map ppp0 192.168.1.0/24 -> 126.96.36.199/32
You can find a more detailed example of a configuration file in /etc/ipnat.conf. We've included this file in "Configuration files for setting up a firewall" in the Examples appendix.
For the above example, when a packet goes out on the ppp0 interface with an IP address of 192.168.1.0 and a netmask of /24, the packet is rewritten within the IP stack, such that its source address is 188.8.131.52 with a netmask of /32. The packet is then sent to the original destination. The system also keeps track of the translated connection in progress, so it can send the response to the correct system.