z/OS UNIX System Services File System Security
Introduction
We in z/OS development often get questions like: “How do I secure UNIX System Services?” It’s a difficult question to answer since z/OS UNIX is an entire operating system within an operating system. It’s akin to asking: “How do I secure z/OS?” You won’t get a quick and easy answer.
But we must start somewhere. In the past, I’ve written presentations that try to separate UNIX security into managing identities and managing the file system. For this article, I’ll focus on the file system. But even that is a very broad topic. So, I’ll start at the top and limit myself to discussing the file system at the data set level.
File system organization
As an end-user, we view the file system as an inverted tree, starting at the root directory (“/”), working down through files and subdirectories, and down through more subdirectories to an indefinite depth. And this is correct.
At a different level, however, you could view it as a coarser-grained structure where “nodes” in the tree are not directories, but individual MVS data sets. zFS data sets, to be precise. Each one contains its own root directory and set of files and subdirectories. These individual building blocks are also referred to as ‘file systems”, and so we must be careful about context when using the term. I’ll use the word “aggregate” in this article. Each aggregate is mounted on a directory (called a “mount point” when used for this purpose) within its parent’s aggregate.
The entire structure is defined in your BPXPRMxx member of SYS1.PARMLIB. It establishes the overall file system organization when you IPL your z/OS system. However, this structure is flexible, and the mount and unmount commands can be used to add, remove, or move aggregates around in the hierarchy. Note that the MOUNT and UNMOUNT commands are also available as TSO commands.
While it is essential that you understand security concepts for individual files and directories (ownership, permission bits, access control lists, etc., which will all be covered in the next article, “UNIX File Security”), you should also understand the security controls that are available at the aggregate level. Various controls are available both through z/OS UNIX, the zFS file system, and your security product, which in my case is RACF.
z/OS UNIX controls
From a UNIX perspective, you can control the mode in which an aggregate is mounted. For example, an aggregate can be mounted in read-write or read-only mode. Read-only mode means that, regardless of the permission bits of a file, or the authority of a user (even a “superuser” with a UID of 0), files cannot be altered. Since security attributes are simply meta-data of a file, they cannot be modified either. An example of where this would be a good practice is an aggregate for binary executables associated with a specific application (these are generally mounted on /usr/lpp). Once the application is installed, you don’t expect these files to change until the next time you need to apply service. Therefore, why would anyone have the need to modify file contents or security attributes? Mounting an aggregate in read-only mode prevents modifications, period.
Speaking of application binaries, some require the set-user-ID or set-group-ID bits to be on. These bits cause the executing user’s authority to temporarily switch to that of the owning user and/or group while the program is running. This is commonly used to temporarily elevate (or at least change) the user’s privilege. As you can imagine, this capability is an attractive target for malicious actors. So, what if a given aggregate does not have any such files, and has no need to ever contain such files? The NOSETUID option of the mount command should be used in such cases where possible. When an aggregate is mounted in this mode, set-user-ID and set-group-ID files are ignored for executables within it. Even in cases where the aggregate cannot be mounted read-only because file modifications need to be made, NOSETUID protects you against these types of modifications by rendering them ineffective.
Finally, you should be aware of the NOSECURITY option of the mount command. Mounting an aggregate with NOSECURITY means, wait for it, no security checks are made for file accesses! Anybody can do anything to any file. It’s the Wild Wild West in there. While there are some restrictions when in this mode (set-user-ID, set-group-ID, program-controlled and APF attributes are ignored, and auditing can still occur), you should make sure there aren’t any aggregates mounted in this mode.
RACF controls
From a RACF perspective, there are a number of classes and resources that can help you lock down aspects of file system security.
First and foremost, aggregates are data sets, and I’m pretty sure RACF can protect data set access 😉. z/OS UNIX commands and APIs access files and directories “from the inside”. That is, there is no data set access (OPEN), per se, on a user’s behalf. Instead, the UNIX kernel and the file system mediate your access to the contents of the aggregate, calling various SAF callable services for things like file access and security attribute changes. However, nothing stops a knowledgeable user from accessing the aggregate “from the outside” unless the data set is protected by a RACF DATASET profile. If a malicious user understands the internal organization of a zFS data set (which is simply a VSAM linear data set), they can access any information within it, and change security attributes.
This applies every bit as much to a user’s personal file system. I’m talking about the zFS aggregate that gets provisioned for a new UNIX user, often using the automount facility, and gets mounted as the /u/userID directory. It might seem safe at first glance to let a user access his or her data any way they want to. But imagine a diabolical user who puts a malicious program into her home directory, and then, “from the outside”, changes that file’s owning UID to 0 and turns on the file’s set-user-ID bit. You get the idea. The best way to prevent this is by using a naming convention for user file systems such that the high-level qualifier (HLQ) is something other than the user’s RACF user ID. That way, they don’t have automatic access “from the outside”.
Before leaving the topic of data set protection, of course you should be protecting your SYS1.PARMLIB data sets with RACF DATASET profiles, and carefully controlling who can make updates, including to BPXPRMxx.
RACF also provides a control against accessing files and directories within a zFS (not HFS or tfs) aggregate, even from file owners, superusers, and users who would otherwise have had access via the SUPERUSER.FILESYS resource in the UNIXPRIV class (but not from users with the AUDITOR or ROAUDIT attribute). By defining a profile in the FSACCESS class that covers the aggregate data set name, you can prevent users from navigating into the aggregate (“crossing a mount point”) using the cd (change directory) command, or by using a path name that crosses that mount point. Once in, you’re in, and normal UNIX file access rules apply. But FSACCESS provides a valuable tool to prevent any access at all from users without a need to reference objects within the aggregate. As an added bonus, the RACF security administrator sets up this protection using the RACF commands with which he is familiar. There is no need to know any shell commands.
The FSEXEC class works in a similar fashion to prevent scripts and programs from being executed from within an aggregate. Often, an attacker’s first action, once gaining access to the system, is to install a program or script that probes the system for weaknesses. Frequently, they target the /tmp directory. This is a special directory that contains temporary files created by legitimate applications on behalf of their users. As such, /tmp is generally defined with “world-write” permission, which, by design, allows any user (or the program they are running) to create files. These are usually non-executable data files, perhaps maintaining state information. This is an excellent candidate to protect with an FSEXEC profile.
Depending on how the system programmer defined this file system, the /tmp directory may be a zFS or tfs file system. A tfs file system is implemented in memory. As such, there is no data set name to use as the profile name. In this special case, you can actually use the literal directory name “/tmp” as your profile name. RACF handles that just fine. The /etc directory generally contains configuration files. This directory resides in a zFS data set, so you would use its actual data set name (for example, OMVS.ZFS.SYS1.ETC).
You may have figured out by this point that the controls I have been describing are most effective in an environment where individual aggregates are used for files and directories that serve a related purpose, at the lowest level of granularity that makes sense. For example, stuffing a lot of disparate directory trees into a single aggregate will require you to permit many different users and groups to an FSACCESS profile, which does not fence a user off, once inside, from other objects unrelated to their job role (but don’t panic too much, access lists can be just as effective; it’s just that they require UNIX shell knowledge). Putting too much stuff in the root directory is particularly troublesome, because FSACCESS cannot be used for the root file system aggregate.
You should employ maximal granularity when it’s within your control and push back against software vendors who package their software in a way that confounds these protections.
Finally, RACF provides protection of the mount command itself, using the SUPERUSER.FILESYS.MOUNT resource in the UNIXPRIV class. READ access allows the basic ability to un/mount aggregates without the SETUID option. UPDATE access allows the same capability with the SETUID option, and allows certain settings to be changed using the chmount command.
Discovering your settings
We’ve covered a bunch of settings and controls, and now you might be asking “How do I see what controls and settings are in effect?” I will assume that you know how to look at PARMLIB and how to check RACF class settings and view profiles. Thus, I will concentrate on the more UNIXy things.
The df shell command lists out all your aggregates, and the verbose option displays your security settings. The output can be voluminous, so it is helpful to redirect the command output to a file for subsequent viewing. If you specify a path name with the df command, it figures out the aggregate name for you and displays its (and only its) attributes.
“But the shell frightens and confuses me!” you say. For the more shellophobic amongst you, have no fear. You never need to leave the TSO command line. The TSO OSHELL command allows you to issue a shell command from the TSO command line.
oshell df -v > df.output
oedit df.output
And voila! You are in a good old-fashioned ISPF edit session on the UNIX file named df.output that you just created in your home directory to contain the output of the df command.
Or, say you just want to find the security settings in the file system data set used for the /etc directory:
oshell df -v /etc
Mounted on Filesystem Avail/Total Files Status
/SYSTEM/etc (ZOS24.ETC.ZFS) 4766/5760 4294967253 Available
ZFS, Read/Write, Device:3, ACLS=Y
Filetag : T=off codeset=0
Aggregate Name : ZOS24.ETC.ZFS
Now you know that the aggregate name is ZOS24.ETC.ZFS and you can see if it is protected in the FSEXEC or FSACCESS class:
RLIST FSACCESS ZOS24.ETC.ZFS ALL
RLIST FSEXEC ZOS24.ETC.ZFS ALL
I also mentioned set-user-ID and set-group-ID bits. How do I find occurrences of those? You can use the shell command aptly named ‘find’! It is well worth your time to familiarize yourself with this command, as it can search for files and directories in any subtree of your file system, using all sorts of criteria. For example, say you have an aggregate named ZFS.APPL.X that is mounted on the /usr/lpp/appx directory, and you want to see if it contains set-user-ID or set-group-ID files. The extra trick here is that these bits are contained in the “file mode” which also contains the permissions bits, so we use the -perm option. We also use the “-“ operator before the octal value to indicate we want to use it as a mask, as opposed to an exact match. Finally, the set-group-ID bit can be applied to a directory, where it has a different meaning. So, we only want to find occurrences of either bits in regular files.
find /usr/lpp/appx -type f \( -perm -4000 -o -perm -2000 \)
(Note: the space after “(“ and before the final “\” are required.)
That was a pretty gnarly shell command, and it took me a few tries to get it right. So again, for the shellophobes, if you would prefer to run queries against file system security data, just like you do with RACF Database Unload (IRRDBU00) output, then I have just the thing for you! Many years ago, I wrote a little utility I called “HFS Unload” and made it available on the RACF downloads site:
https://github.com/IBM/IBM-Z-zOS/tree/main/zOS-RACF/Downloads/ZFSUnload
It can be run as a shell command or a batch job, and, like find, can work on any subtree(s) of the file system you desire. It creates a flat file with records describing individual file/directory security attributes as well as file system aggregate security attributes. Like DB Unload (and SMF Unload), it comes with sample LOAD and TABLE statements for DB2. The output is every bit as consumable by a REXX exec or ICETOOL job as well. The HFSUnloadReadMe.pdf file contains the documentation.
It’s possible that there are software vendors whose products can also be helpful.
zFS controls
Let’s not forget that the most fundamental way to provide privacy of data is by encrypting it. Encryption of zFS data sets has been available since z/OS 2.3. Since 2.3 is now out of service, you should be able to encrypt zFS on all systems, including those sharing file system data in a sysplex.
When a zFS aggregate is encrypted, the security attributes are encrypted as well as the actual file data. Thus, the ‘outside-in’ attack described above is not possible, but data corruption is certainly possible, and so DATASET profile protection is still crucial.
The zfsadm encrypt command can encrypt existing file systems. The file system can be encrypted while it is in use.
Conclusion
I hope I’ve given you some helpful information on how to locate potential vulnerabilities in the security of your z/OS UNIX file system, and tips on how to lock it down. You can now see how some controls can be applied at a data set level using familiar RACF concepts, and that some even override traditional UNIX security rules.
Some things you can do today are:
· Review your BPXPRMxx members and specify read-only and NOSETUID modes wherever possible. Make sure no file systems are mounted with the NOSECURITY attribute.
· Inspect your SUPERUSER.FILESYS.MOUNT profile in the UNIXPRIV class. Carefully review UPDATE access, as it allows mounting with the SETUID option.
· Make sure /tmp and other applicable aggregates are protected with an FSEXEC profile.
· Protect as many zFS aggregates as necessary with FSACCESS profiles.
· Make sure the zFS aggregates for user file systems don’t use the userID as the high-level qualifier.
· Implement zFS encryption for aggregates holding sensitive data. Or encrypt them all!
Now have at it!
Brought to you by Bruce Wells
Bruce Wells is the RACF Product Owner, and has been a RACF design and developer on both the z/VM and z/OS versions for over 30 years.