Using code signing on z/OS

There is an IBM document Task roadmap for program signing and signature verification, this provides some of the information, and some of the information is wrong!

I’ve documented Signing load modules on z/OS.

I hit various problems. The return code from the program signing function are documented here.

Using signed modules on your system

You have been sent some modules which have been signed, and you want to user the signature verification on these modules.

One-of system wide setup

Define the system wide keyring

For the validation of signed modules there is one system wide key ring. It needs to have the signing certificates (or the CA certificates) for all of the modules being used.

RACDCERT ADDRING(VERIFY) ID(COLIN ) 

* now the profile
RDEFINE RDATALIB COLIN.VERIFY.LST UACC(NONE)
PERMIT COLIN.VERIFY.LST CLASS(RDATALIB) ID(COLIN ) ACCESS(UPDATE)
SETROPTS RACLIST(RDATALIB) REFRESH

Connect the IBM CA to the keyring

You need to connect the IBM Provided Certificate authority, to be able to verify the signature of IBM provided modules. See Listing of RACF supplied certificates.

RACDCERT ID(COLIN )CONNECT(RING(VERIFY) - 
CERTAUTH -
LABEL('STG Code Signing CA - G2')

RACDCERT LISTRING(VERIFY) ID(COLIN )

Note the lower case name.

Tell the certificate certificate facility which key ring to use.

 RDEFINE FACILITY IRR.PROGRAM.SIGNATURE.VERIFICATION  - 
APPLDATA('COLIN/VERIFY')

This ring has the certificates needed to verify.

Define the program that does the signature validation.

RDEFINE PROGRAM IRRPVERS ADDMEM('SYS1.SIEALNKE'//NOPADCHK) UACC(READ)- 
SIGVER(SIGREQUIRED(YES) FAILLOAD(ANYBAD) SIGAUDIT(ANYBAD))

Run the IRRVELD job

I had to run this when setting up for the first time. When I next IPLed, I did not need to run it. I think that RACF checks to see if the above RACF profile exists – if so – loads the code.

See here.

//IRRVERLD JOB                        
//IRRVERLD EXEC PGM=IRRVERLD

When successful, this starts and ends with return code 0 and

ICH448I RACF program signature verification function is loaded. Program signature verification is available on this system.

Define your programs that need validation

RDEFINE PROGRAM ZZZDATA  ADDMEM('COLIN.ZWIRESHA.LOAD'//NOPADCHK) - 
UACC(READ) -
SIGVER(SIGREQUIRED(YES) FAILLOAD(ANYBAD) SIGAUDIT(ANYBAD))

SETROPTS WHEN(PROGRAM) REFRESH

Where my program is called ZZZDATA and it is loaded from COLIN.ZWIRESHA.LOAD. The definition SIGVER(SIGREQUIRED(YES) FAILLOAD(ANYBAD) SIGAUDIT(ANYBAD)) says that signature is required, and if verification fails do not load the module, and write an audit record.

Each userid (or group) needs a RACF profile to specify which keyring to use to sign the programs

If you want to do IPL data signing for Validated Boot for z/OS you need the profile IRR.PROGRAM.V2.SIGNING…..

If you want to validate a general program you need the profile IRR.PROGRAM.SIGNING….

Where you can specif userid, group, or group and userid.

  • IRR.PROGRAM.SIGNING.userid
  • IRR.PROGRAM.SIGNING.goup
  • IRR.PROGRAM.SIGNING.group.user
                              
RDELETE FACILITY IRR.PROGRAM.SIGNING.COLIN
RDEFINE FACILITY IRR.PROGRAM.SIGNING.COLIN -
APPLDATA('SHA256 COLIN/SIGNING')
PERMIT IRR.PROGRAM.SIGNING.COLIN CLASS(FACILITY) -
ID(COLIN) ACCESS(CONTROL)
SETROPTS RACLIST(FACILITY) refresh

This says for userid COLIN use SHA256 and keyring COLIN/SIGNING. It will use the default key in the keyring.

You sign a module at bind time, by specifying the binder options SIGN=YES.

I added sign=yes to the binder JCL

Create a certificate to sign it

RACDCERT ID(COLIN) GENCERT -                                 
SUBJECTSDN(CN('10.1.1.2') -
O('SIGNER521') -
OU('SSS')) -
ALTNAME(IP(10.1.1.2))-
SIZE(2048) -
NOTAFTER( DATE(2026-04-22) TIME(15:22:00) ) -
KEYUSAGE(HANDSHAKE DOCSIGN) -
SIGNWITH (CERTAUTH LABEL('SIGNCA')) -
WITHLABEL('SIGNER521')

RACDCERT id(COLIN) ALTER(LABEL('SIGNER521'))TRUST

SETROPTS RACLIST(DIGTCERT) REFRESH
RACDCERT ID(COLIN )CONNECT(RING(SIGNING) -
ID(COLIN) -
DEFAULT -
LABEL('SIGNER521') )

It seems to need KEYUSAGE HANDSHAKE and DOCSIGN.

It needed the default RSA size 2048. It did not work with an ECC algorithm.

It needs to be the default in the keyring.

Execute the program

When you come to execute the signed program, the system uses the keyring defined in the

IRR.PROGRAM.SIGNATURE.VERIFICATION CLASS(FACILITY) profile. I believe this has the certificates needed to verify the programs signature.

I used keyring COLIN/VERIFY.

IEW2494E 9116 A PROBLEM WAS ENCOUNTERED WITH THE SETUP OF THE DIGITAL CERTIFICATES REQUIRED FOR PROGRAM SIGNING. RACF RETURNED SAF RETURN CODE 8 RACF RETURN CODE 8 RACF REASON CODE 00000070.

R_PgmSign 70 Ver Key ring or token does not exist or does not contain a default certificate.

Signing load modules on z/OS

There is an IBM document Task roadmap for program signing and signature verification, this provides some of the information, and some of the information is wrong!

This blog post describes how to set up your environment to sign load modules. I’ve documented Using code signing on z/OS

I hit various problems. The return code from the program signing function are documented here.

Create RACF profiles

Each userid (or group) needs a RACF profile to specify which keyring to use to sign the programs

If you want to do IPL data signing for Validated Boot for z/OS you need the profile IRR.PROGRAM.V2.SIGNING…..

If you want to validate a general program you need the profile IRR.PROGRAM.SIGNING….

Where you can specify userid, group, or group and userid.

  • IRR.PROGRAM.SIGNING.userid
  • IRR.PROGRAM.SIGNING.goup
  • IRR.PROGRAM.SIGNING.group.user
                              
RDELETE FACILITY IRR.PROGRAM.SIGNING.COLIN
RDEFINE FACILITY IRR.PROGRAM.SIGNING.COLIN -
APPLDATA('SHA256 COLIN/SIGNING')
PERMIT IRR.PROGRAM.SIGNING.COLIN CLASS(FACILITY) -
ID(COLIN) ACCESS(CONTROL)
SETROPTS RACLIST(FACILITY) refresh

This says for userid COLIN use SHA256 and keyring COLIN/SIGNING. It uses the default key in the keyring.

How to sign a load module

You sign a module at bind time, by specifying the binder options SIGN=YES.

I added SIGN=YES to the binder JCL

When it failed, there were messages like

IEW2494E 9116 A PROBLEM WAS ENCOUNTERED WITH THE SETUP OF THE DIGITAL CERTIFICATES REQUIRED FOR PROGRAM SIGNING. RACF RETURNED SAF RETURN CODE 8 RACF RETURN CODE 8 RACF REASON CODE 00000098.

These are from the R_PgmSignVer (IRRSPS00): Program Sign and Verify callable service. See the return codes. The return codes in the messages are in hex; in the documentation they are in decimal. 0x98 is decimal 152. 152 is CA or signing certificate is expired or not yet active. This was true – my signing certificate had expired.

Create a certificate to sign load modules.

RACDCERT ID(COLIN) GENCERT -                                 
SUBJECTSDN(CN('10.1.1.2') -
O('SIGNER521') -
OU('SSS')) -
ALTNAME(IP(10.1.1.2))-
SIZE(2048) -
NOTAFTER( DATE(2026-04-22) TIME(15:22:00) ) -
KEYUSAGE(HANDSHAKE DOCSIGN) -
SIGNWITH (CERTAUTH LABEL('SIGNCA')) -
WITHLABEL('SIGNER521')

RACDCERT id(COLIN) ALTER(LABEL('SIGNER521'))TRUST

SETROPTS RACLIST(DIGTCERT) REFRESH
RACDCERT ID(COLIN )CONNECT(RING(SIGNING) -
ID(COLIN) -
DEFAULT -
LABEL('SIGNER521') )

It seems to need KEYUSAGE HANDSHAKE and DOCSIGN.

It needed the default RSA size 2048. It did not work with an ECC algorithm.

It needs to be the default in the keyring.

Code signing – error messages

The messages I received were from R_PgmSignVer (IRRSPS00 or IRRSPS64): Program Sign and Verify

Return code hex 70, decimal 112

  • ICH440I Program signature error 0x10/0x00000070 for program IRRPVERS in library SYS1.SIEALNKE.
  • The program was not loaded.
  • ICH442I The digital signature appears to be valid but the root signer is not trusted.
  • ICH451I RACF encountered an error while attempting to load the program verification module. Operation code = X’06’.
  • Return code X’00000306′ and Reason code X’00000040′.
  • Supplemental diagnostic code 1 = X’00000000′.
  • Supplemental diagnostic code 2 = X’00000000′.
  • Supplemental diagnostic code 3 = X’00000000′.
  • Supplemental diagnostic code 4 = X’00000000′.
  • Supplemental diagnostic code 5 = X’00000000′.

The certificate CERTAUTH LABEL(‘STG Code Signing CA – G2’) was not trusted.

R_PgmSignVer return codes code 0x70 is 112. Key ring or token does not exist or does not contain a default certificate.

I had mis typed the certificate name.

Action:

The the name of the keyring, there is a default certificate, and the certificate has not expired.

Return code hex 64, decimal 100

At execution time I got.

ICH440I Program signature error 0x10/0x00000064 for program ZZZDATA in library COLIN.ZWIRESHA.LOAD. The program was not loaded. ICH442I The digital signature appears to be valid but the root signer is not trusted.

code 0x64 is decimal 100 which is

The program appears to be correctly signed but one of the following conditions exists:

  • The root CA certificate in the zOSSignatureInfo structure of the program object is not connected to the signature-verification key ring.
  • The root CA certificate is marked NOTRUST.

The CA of the signing code was not in the verify keyring at execution time.

Action:

Connect the certificate or its CA to the verify keyring.

Return code hex 88, decimal 136

IEW2494E 9116 A PROBLEM WAS ENCOUNTERED WITH THE SETUP OF THE DIGITAL CERTIFICATES REQUIRED FOR PROGRAM SIGNING. RACF RETURNED SAF RETURN CODE 8 RACF RETURN CODE 8 RACF REASON CODE 00000088.

The default certificate in the key ring or token does not have code signing capability.

  • When Num_parms = 10, the keyUsage extension is optional. If the extension is present, the digitalSignature bit must be set.
  • When Num_parms = 11, the keyUsage extension must be present and the digitalSignature bit must be set.

The real reason is “HANDSHAKE” or “DOCSIGN” was missing from the KEYUSAGE().

Action:

Fix the keyusage field.

Return code hex 90, decimal 144

IEW2494E 9116 A PROBLEM WAS ENCOUNTERED WITH THE SETUP OF THE DIGITAL CERTIFICATES REQUIRED FOR PROGRAM SIGNING. RACF RETURNED SAF RETURN CODE 8 RACF RETURN CODE 8 RACF REASON CODE 00000090.

The key type of one or more certificates in the key ring or token is not supported. This reason code is also issued for the following conditions:

  • When Num_parms = 11, the signing key can be stored in ICSF, but it must be a 521-bits NIST Elliptic Curve Cryptography (ECC) key. Also, the key size of any other certificates in the key ring or token must be at least 2048 bits for RSA keys, or 224 bits for NIST ECC and Brainpool ECC keys.
  • When Num_parms = 10 and an RSA CA certificate in the chain has a key size less than 2048 bits.

This was caused by using a key with

NISTECC -
SIZE(521 ) -

using the followin

RSA
SIZE(2048) -

Works.

Action:

Use RSA key with size 2048;

How do you download and use a dataset from z/OS.

Transferring a dataset from z/OS to Windows or Linux and using it can be a challenge.

A record in a data set on z/OS has a 4 byte Record Descriptor Word on the front of the record. The first two bytes give the length of the record (and the other two bytes are typically 0)

FTP has two modes for transferring data ASCII and BIN.

ASCII

With ASCII mode, FTP reads the record,

  • Removes the RDW
  • Converts it from EBCDIC to ASCII
  • Adds a “New Line” character to the end of data
  • Sends the data
  • Writes the data to a file stream.

On Unix and Windows a text file is a long stream of data. When the file is read, a New Line character ends the logical record, and so you display the following data on a “New Line”.

Binary mode

Binary mode is used when the dataset has hexadecimal content, and not just printable characters. The New Line hex character could be part of a some hexadecimal data, so this character cannot be used to delineate records.

FTP has an option for RDW

quote site RDW

The default is RDW FALSE.

If RDW is FALSE then FTP removes the RDW from the data before sending it. At the remote end, the data is a stream of data, and you have no way of identifying where one logical record ends, and the next logical record starts.

If RDW is TRUE, then the 4 byte RDW is sent as part of the data. The application reading the file can read the information and calculate where the logical record starts and ends.

For example on z/OS the dataset has (in hex) where the bold data is displayed when you edit or browse the dataset. The italic data is not displayed.

00040000C1C2C3C4
00020000D1CD2
00050000E1E2E3E4E5

If the data was transmitted with RDW FALSE the data in the file would be

C1C2C3C4D1D2E1E2E3E4E5

If the data was transmitted with RDW TRUE the data in the file would be

00040000C1C2C3C400020000D1CD200050000E1E2E3E4E5

Conceptually you can process this file stream using C code:

short RDW;  // 2 byte integer
short dummy; // 2 byte integer

RDW = fread(2); // get the length
dummy = fread(2); // ignore the 0s
mydata = fread(RDW -4); // -4 for the RDW already read

...
RDW = fread(2); // get the length
dummy = fread(2); // ignore the 0s
mydata = fread(RDW -4); // -4 for the RDW already read

(Thanks to pjfarley3 who pointed out the RDW length includes the 4 byte RDW – so the application data length is RDW -4.)

In practice this will not work because z/OS has numbers which are Big Endian, and X86 and ARM machines are Little Endian. (With Big Endian – the left byte is most significant, with Little Endian, the right bit is most significant – the bytes are transposed.)

On z/OS 0x0004 is decimal 4. On X86 and ARM 0x0400 is 4.

In practice you need code on X86 and ARM, like the following, to get the value of a half word from a z/OS data set.

char RDW[2];  // 2 characters
RDW = fread(2); // get the length
length = 256 * RDW[0] + RDW[1]

and similarly for longer integers.

Python

If you are using the Python struct facility, you can pass a string of data types and get the processed values.

  • The string “>HH” says two half words, and the > says the numbers are Big Endian.
  • The string “<HH” says two half words and the < says they are Little Endian
  • The string “HH” says two half words – read in the default representation.

Conversion

You’ll need to do your own conversion from EBCDIC to ASCII to make things printable!

How do you trust a file?

I was asked this question by someone wanting to ensure their files have not been hacked. In the press there are articles where bad guys have replaced some code with code that steals credentials, or it allows an outsider access to your machine. One common solution to trusting a file uses cryptography.

There are several solutions that do not work

Checking the date of a file.

This does not work because there are programs that allow you to change the date and time of files.

Checking the number of bytes

You can list a file’s properties. One property is the size of the file. You could keep a list of file, and file size.

There are two problems

  1. You can change the contents of the file without changing the size of the file. I’ve done this. Programs used to have a patch area where skilled people could write some code to fix problems in the program.
  2. Someone changes the size of the file – but also changes your list to reflect the new size, and then changes the date on file and your list so they look as if they have not changed.

Hashing the file contents

Do a calculation on the contents of the file. A trivial function to implement and easy to exploit, is to treat each character as an unsigned integer, and add up all of the characters.

A better hashing function is to do a calculation cs = mod(c **N,M). For example when the current character is 3, n is 7 and m is 13; find the remainder of 3*3*3*3*3*3*3 when divided by 13, the answer is 3. N and M should be very large. Instead of using one character you take 8 or more. You then apply the algorithm on the file.

cs = 0
do 8 bytes of the file at a time
cs = mod((cs + the 8 bytes)** N,M)
end
display cs

Some numbers N and M are better that others. Knowing the value cs, you cannot go back and recreate the file.

If you just store the checksum value in a file, then the bad guys can change this file, and replace the old checksum with the new checksum of the file with their change. It appears that doing a checkum on the file does not help.

Cryptography to the rescue

To make things secure, there are several bits of technology that are required

  • Public and private keys
  • How do I trust what you’ve sent me

Public and private keys

Cryptography has been around for thousands of years. This typically had a key which was use to encrypt data, and the same key could be used to decrypt the data.

The big leap in cryptography was the discovery of asymmetric keys where you need two keys. One can be used for encryption, and you need another for decryption. You keep the one key very secure (and call it the private key) and make the other key publicly available (the public key). Either key can be used to encrypt, and you need the other key to decrypt.

The keys can be used as follows

  • You encrypt some data with my public key. It can only be decrypted by someone with my private key.
  • I can encrypt some data with my private key and sent it to you. Anyone with my public key can decrypt it. In addition, because they had to use my public key, then they know it came from me (or, to be more accurate, someone with my private key).

How do I trust what you’ve sent me

I would be very suspicious if I received an email saying

This is your freindly bank heree. Please send us your bank account details with this public key. Pubic keys are very safe and we are the only peoples who can decrypt what you send me.

Digital certificates and getting a new passport

A public certificate has

  • Your name
  • You address such as Country=GB, Org=Megabank.com,
  • Your public key
  • Expiry date
  • What the certificate can be used for

I hope the following analogy explains the concepts of digital certificates.

Below are the steps required to get a new passport

  • You turn up at the Passport Office with your birth certificate, a photograph of you, a gas bill, and your public certificate.
  • The person in the office checks
    • that the photo is of you.
    • your name is the same as the birth certificate
    • the name on the gas bill matches your birth certificate
    • the address of the gas bill is the same as you provided for your place of residence.
  • The office creates the passport, with information such as where you live (as evidenced by the gas bill)
  • The checksum of your passport is calculated.
  • The checksum is encrypted with the Passport Office’s PRIVATE key.
  • The encrypted checksum and the Passport Office’s PUBLIC key are printed, and stapled to the back of the passport
  • The passport is returned to you. It has been digitally signed by the Passport Office.

How do I check your identity?

At the back of MY passport is the printout of the Passport Offices’ public key. I compare this with the one attached yo your passport – they match!

I take the encrypted checksum from your passport, and decrypt it using the Passport Office’s public key (yours or mine – they are the same). I write this on a piece of paper.

I do the same checksum calculation on your passport. If the value matches what is on the piece of paper, then you can be confident that the passport has not been changed, since it was issued by the Passport Office. Because I trust the Passport Office, I trust they have seen your birth certificate, and checked where you live, and so I trust you are who you say you are.

But..

Your passport was issued by the London Passport Office, and my passport was issued by the Scottish Passport Office, and the two public certificates do not match.

This problem is solved by use of a Certificate Authority(CA)

Consider a UK wide Certificate Authority office. The Scottish Passport Office sent their certificate (containing, name address and public key) to the UKCA. The UKCA did a checksum of it, encrypts the checksum with the UKCA PRIVATE key, attached the encrypted checksum, and the UKCA public certificate to the certificate sent in – the same process as getting a passport.

Now when the Scottish Passport office process my passport, they do the checksum as before, and affix the Scottish Passport Offices’ public certificate as before… but this certificate has a copy of the UKCA’s certificate, and the encrypted checksum stuck to it. The passport now has two bits of paper stapled to it, the Scottish Passport Office’s public certificate, and the UKCA’s public certificate.

When I validate your passport I see that the London Passport office’s certificate does not match the Scottish Passport Offices certificate, but they have both been signed by the UKCA.

  • I compare the UKCA’s public certificates – they match!
  • I decrypt the checksum from the London office using the UKCA’s certificate and write it down
  • I do the same checksum calculation on the London offices’s certificate and compare with what is written down. They match – I am confident that the UKCA has checked the credentials of the London office
  • I can now trust the London certificate, and use it to check your passport as before.

What happens if I do not have the UKCA certificate

Many “root” certificates from corporations, are shipped on Windows, Linux, z/OS, Macs etc. The UKCA goes to one of these corporations, gets their certificate signed, and includes the corporations certificate attached to the UKCA certificate. Of course it costs money to get your certificate signed by one of these companies

You could email the UKCA certificate with the public key to every one you know. This has the risk that the bad guys who are intercepting your email, change the official UKCA certificate with their certificate. Plan b) would be to ship a memory stick with the certificate on it – but the same bad guys could be monitoring your mail, and replace the memory stick with one of theirs.

How does this help me trust a file?

The process is similar to that of getting a passport.

My “package” has two files abx.txt and xyz.txt

At build time

  • Create the files abc.txt and xyz.txt
  • Calculate the checksum of each file, and encrypt the value – this produces a binary file for each abc.txt.signature
  • Create a directory with
    • Your public certificate/public key
    • A directory containing all of the signature files
    • A list of all of the files in the signature directory
    • A checksum of the directory listing. directory.list.signature

You ship this file as part of your product.

When you install the package

  • Validate the certificate in the package against the CA stored in your system.
  • Decrypt the list of files in the directory (directory.list.signature). Check the list of files is valid
  • For each line in the directory list, go through the same validations process with the file and it’s signature.

For the paranoid

Every week calculate the checksum of each file in the package and sent it to a remote site.

At the remote site compare the filename, checksum combination against last week’s values.

If they do not match, the files have been changed.

Of course if your system has been hacked, the bad guys may be intercepting this traffic and changing it.

How do I do it?

I have a certificate mycert.pem, and my private key mycert.private.pem. It was signed by ca256.

Build

Run the command against the first file

openssl dgst -sign mycert.key.pem abc.txt   > abc.txt.signature

Move the abc.txt.signature to the package’s directory,

Create the trust package

/
.. /mycert.pem
.. /directory.list.txt
.. /directory.list.txt.signature
.. /signatures/
.. .. /abc.txt.signature
.. .. /xyz.txt.signature

Validate the package

Validate the certificate in the package.

openssl verify -CAfile ca256.pem mycert.pem 

extract the public key from the certificate.

openssl x509 -pubkey -noout -in mycert.pem > mycert.pubkey

validate the checksum of the abc file using the public key.

openssl dgst -verify ./mycert.pubkey  -signature abc.txt.signature  abc.txt

Does it work with data sets ?

On z/OS I created a signature file with

openssl dgst -sign tempcert.key.pem  "//'COLIN.JCL(ALIAS)'"  > jcl.alias.signature

and validated it with

openssl dgst -verify tempcert.pubkey -signature jcl.alias.signature  "//'COLIN.JCL(ALIAS)'"   

Formatting SYSADATA from HLASM

I wanted to extract information about DSECTS from the SYSADATA output from compiling an assembler program on z/OS.

On the whole it was pretty easy – but had some surprises!

I’ve put some Python code up on github. It runs on my z/OS.

Where is the record layout documented?

The record layout is documented in HLASM V1R6 Programmer’s Guide. Dsects for the various types are provided in HLA.SASMMAC1(ASMADATA).

I used record type 0x0042 for symbols. To get a record in this section of the ADATA it needs a label.

For example

      DSECT COLIN2
ABCD DS CL8
ABCDE DS CL8

This will not produce a record for the DSECT – because it does not have a label.

ESDID – section names

Each DSECT or CSECT will have an External Symbol Directory ID.

  • CSECT start at 1, and increment, so 1,2
  • DSECTs start at -1 and decrement 4294967295= 0xffffffff(-1), 4294967294 = 0xfffffffe(-2)

Field order

The order of records seems to be random. The CSECT/DSECT statement is often after some fields in the CSECT/DSECT.

To find the xSECT for each symbol, I saved the SECT name and ESDID, and post processed the list of symbols by adding the xSECT information afterwards from the ESDID.

Field offsets

The offsets in each record seem to be the offset from the first instruction. I had to save the offset from the CSECT statement, then post process the records to calculate (offset of symbol in CSECT) = symbol offset – start_of_CSECT offset.

“Problems”

Missing data

To get a record into the ADATA ensure it has a label.

Output from my code

     ESDID              Symbol  Offset  Length TypeA       SymType    CSECT
4294967295 ABCD 0 8 C OrdinaryLabel COLIN2
4294967295 COLIN2 0 1 J DSECT COLIN2
4294967295 ABCDE 8 8 C OrdinaryLabel COLIN2
1 CSQ6LOGP 0 1 J CSECT CSQ6LOGP
1 LOGP 0 8 D OrdinaryLabel CSQ6LOGP
1 LOGPID 0 2 R OrdinaryLabel CSQ6LOGP
1 LOGPLL 2 2 R OrdinaryLabel CSQ6LOGP
1 LOGPEID 4 4 C OrdinaryLabel CSQ6LOGP
1 LOGPMRTU 8 2 R OrdinaryLabel CSQ6LOGP
1 LOGOPT1 10 1 R OrdinaryLabel CSQ6LOGP
1 LOGOPT2 11 1 R OrdinaryLabel CSQ6LOGP
1 LOGPMCOF 12 2 R OrdinaryLabel CSQ6LOGP
1 LOGPOBPS 16 4 R OrdinaryLabel CSQ6LOGP
1 LOGPIBPS 20 4 R OrdinaryLabel CSQ6LOGP
1 LOGPARCL 24 4 R OrdinaryLabel CSQ6LOGP
1 LOGPWRTH 30 2 R OrdinaryLabel CSQ6LOGP
1 LOGPLVL 32 7 C OrdinaryLabel CSQ6LOGP
1 LOGPLVLN 39 1 R OrdinaryLabel CSQ6LOGP
1 LOGPDMIN 40 2 R OrdinaryLabel CSQ6LOGP
1 LOGPDSEC 42 2 R OrdinaryLabel CSQ6LOGP
1 LOGPCOMP 44 4 R OrdinaryLabel CSQ6LOGP
1 LOGPEND 256 1 U EQU CSQ6LOGP

Re-entrant assembler macros in z/OS

In a C program on z/OS you can code assembler macros. See Putting assembler code inside a C program

I wanted to use a WTO/WTOR macro to put a message onto the operator console. This took a few hours to get working, firstly I needed to understand using Re-entrant macros, and secondly see Using Re-entrant assembler macros in C ASM().

Background to using re-entrant macros in assembler

Programming in assembler

You can write a macro like

   WTO  'Colins message'

This generates code

         WTO   'Colin'               

BRAS 1,LABEL1 Set register 1 to the following DC and jump to the label
LENDATA DC AL2(9)
DC B'0000000000000000'
DC C'Colin'
LABEL1 DS 0H
SVC 35

This works.

I can pass a string to the macro – a half word length followed by the data – like LENDATA above. This makes the WTO macro more complex.

You can pass a variable content message to WTO. The code following

  • creates data in the correct format (length followed by the data).
  • The WTO creates the data structure
  • The WTO updates the data structure from the data passed to the macro
* define the variable data, and point register 2 to it
BRAS 2,OVERTEXT
DATA DC AL2(5) Length of string
DC C'Colin' the data to display

* invoke WTO. (2) says the address of the data is in register 2
OVERTEXT DS 0H
WTO TEXT=(2)

* This generates...
CNOP 0,4
BRAS 1,TWOLABA branch around definition
* The WTO data structure
DC AL2(8) TEXT LENGTH
DC B'0000000000010000' MCSFLAGS
TEXTADDR DC AL4(0) MESSAGE TEXT ADDRESS
..
* The instructions to update the data structure from the passed data
WTOLABA DS 0H
LR 14,1 FIRST BYTE OF PARM LIST
SR 15,15 CLEAR REGISTER 15
AH 15,0(1,0) ADD LENGTH OF TEXT + 4
AR 14,15 FIRST BYTE AFTER TEXT
ST 2,4(0,1) STORE TEXT ADDR INTO PLIST
SVC 35 ISSUE SVC 35

Where

  • BRAS 2,OVERTEXT saves in register 2 the address of data, then branches to the label OVERTEXT
  • BRAS 1,WTOLABA saves in register 1 the address of the structure following the instruction, then branches to label WTOLABA
  • ST 2,4(0,1) this saves what register 2 points to – my data; and stores it at the front of the control block data in field TEXTADDR

This fails to execute because the whole program is Re-entrant (RENT) and so the whole program is read only. The program is trying to store register 2 into read only storage.

Solving the RENT problem.

The problem of trying to write into read only storage is solved splitting the above code into two parts and using thread read write storage.

  • define the structure for the constants in read only storage
  • copy the read only structure to thread read write storage
  • use the thread read write storage for the request.

Define the structure for the constants in read only storage

You use an extra parameter on the macro call

WTOS      WTO TEXT=(),MF=L  
ETWOS DS 0H

This generates code

WTOS     WTO   TEXT=(),MF=L                                       
WTOS DS 0F
DC AL2(8) TEXT LENGTH
DC B'0000000000010000' MCSFLAGS
DC AL4(0) MESSAGE TEXT ADDRESS
...
DC AL4(0) WSPARM ADDRESS
ETWOS DS 0H

This has created the data structure. The data is of length EWTOS-WTOS

Copy the read only structure to thread read write storage

In assembler you can use MVC or MVCL to copy from the read only data to thread read/write storage.

Use the thread read write storage for the request.

If the static part of the structure was copied to the block of storage userdata, the code below will issue the WTO request

WTOL   WTO TEXT=((2)),MF=(E,userdata)

This generates the code to update the structure in read only storage

         WTOR  TEXT=((2)),MF=(E,userdata)                                
LA 1,userdata LOAD PARAMETER REG
LR 14,1 FIRST BYTE OF PARM LIST
SR 15,15 CLEAR REG 15
AH 15,0(1,0) ADD LENGTH OF TEXT + 4
AR 14,15 ADDR OF BYTE AFTER TEXT
ST 2,4(1,0) MOVE TEXT INTO PARM LIST
OI 4(14),B'00000000' SET EXTENDED MCS FLAGS
OI 5(14),B'10000000' SET EXTENDED MCS FLAGS2
SVC 35 ISSUE SVC 35

Note:

Some z/OS components use MF=(S,name) to generate the Static structure with the specified name, others use MF=(L).

They all use MF=(E,name).

Moving to the z/OS standard image and onward

For vendors and people like me who used ZD&T or zPDT to run z/OS on an IBM provided emulator on Linux, moving to the new standard image is a challenge.

Below are my thoughts on how to make it easier to use the standard image.

What does migration mean?

The term migration may mean different things to different people.

  • “Production customers” have a z/OS image, and they refresh the products, while keeping userid, user datasets etc. the same. The products (from IBM and vendors) gradually changes over time, typically changing every 3-6 months. This process is well know, and has been used over many decades.
  • With the IBM standard image, IBM makes a new level of z/OS available, and you have to migrate userids, datasets etc into the image. Every 3-6 months there may be a new image available. Moving from one level of standard image to another level of standard image is new and not documented. It looks easy to do it wrong, and make migration hard. It may take time to migrate to the first standard image, but moving to later images should take no more than half an hour.

This blog post is to suggest ways of making it easy to set up the to use the standard image.

Moving to the first standard image may mean a lot of work, but if you do it the right way moving on should be easy.

Setting the direction

My recommendations are (I would welcome discussion on these topics).

A couple of years ago I wrote a series of blog post starting with Migrating an ADCD z/OS release to the next release. A lot of the information is still relevant. Below I’ve tried to refine it for the migration to the standard image.


Restrict what you put into the master catalog.

You can restrict what user put into the master catalog. For example, enforce every data set High level qualifier has a RACF profile, and only allow user catalog entries to be added to the catalog by general users.

See

Ensure you use a user catalog

If your datasets are in a user catalog, then to go to the next standard image, you just import the user catalog. If you’ve cataloged dataset in the master catalog, then these are not immediately transferable to a new system.

Use USER. datasets, not SYS1. datasets

You can configure z/OS so it uses parmlib and proclib datasets you specify. On the ZD&T there are USER.Z31B PROCLIB, PARMLIB, CLIST datasets etc. You can copy/use these on each new standard image.

If you have changed ADCD.* or SYS1.* datasets, you can use ISPF 3.4, then sort on the “changed” column to see members changed since you first used the system. Then move them to the USER.* dataset.

Create resources using JCL rather than issuing commands, or using the ISPF panels

Use JCL to issue commands in batch TSO, rather than issue the commands manually. For example with the standard image you may get one userid (IBMUSER), and you want to create more userids. Have a JCL member with the commands to create the additional userid commands.

Once created, you just submit the JCL for the follow-on standard image.

Have an ordering to the members in your migration dataset.

If you have to define a group before you create a userid which uses this group, then have members R1GROUP, R2USER1, or have multiple PDSEs, eg COLIN.DO1GROUP.JCL, COLIN.DO2USERS.JCL. where the members within a data set can be issued in any order.

OMVS file systems

I have multiple ZFS (file systems) which I mount on the z/OS image. If these are cataloged in the user catalog, they can be mounted on the new system and used.

You need to think about where to mount them. If the new image has been configured to use automount, this can cause problems. Automount is an OMVS facility which can create a ZFS and mount it for a user. You can allocate a ZFS on a per userid basis, so if one userid use lots of disk space, it does not affect other users. They just run out of space.

When automount is active on the /u directory, if I try to mount my file system on /u, for example /u/colinzfs, the mount will fail because /u/colinzfs is already allocated.

You need to use another directory perhaps /my to mount your ZFS on.

If user’s home directory is something like /my/colin, SSH certificates will be available on the new system, without having to set them up again.

Changing files in system file systems

Try to avoid changing the system file systems, for example /etc/ /var, /usr/

If you have changed the system file systems, see here to see which files have changes since you started using the current image, and move them to your own file system.

Userids and OMVS

You can use the RACF autoid facility which allocates a UID for the userid. This means you do not need to mange the list of UIDs. This makes life easier for an administrator, but harder for a standard image user.

If you use the autoid on the current system you may get an UID such as 990021. On the newer image, your userid may be given a difference UID – depending on the order and number of requests made. Having a different UID can cause problems when using your ZFS. For example the files for my userid COLIN have owner with UID 990021. On the newer system, I may get UID 990033. As this UID is different to 990021, I will not have access to my files.

You should consider explicitly allocating a UID which stays with the user.

If you want to extract RACF profiles from the current system. See the extract program. This will create the RACF command needed to define the profiles. You can specify userids, datasets or classes.

Certificates

You can use RACF commands to display and extract keyring information, and certificates (public and private parts). These can be imported on the newer system. This means your client applications will continue to work.

ICSF

You can configure which data sets ICSF uses in the (CSFPRMXX) member in parmlib. Mine are prefixed with COLIN…

Started tasks

Many started tasks associated with OMVS, (or TCPIP) store configuration in /etc/. For example the file /etc/hosts and the directory /etc/ssh.

You may be able to change the started tasks to use files in your ZFS.

For example

//SSHD    PROC 
//SSHD EXEC PGM=BPXBATCH,REGION=0M,TIME=NOLIMIT,
// PARM='PGM /usr/sbin/sshd -f /my/etc/ssh/sshd_config '

What packages are installed?

You can issue

zopen query -i > installed 

to see what is installed

This gave me

Package   Version  File                               Releaseline             
bash 5.3.9 bash-5.3.20260204_143226.zos STABLE
curl 8.18.0 curl-8.18.0.20260205_151329.zos STABLE
git 2.53.0 git-v2.53.0.20260212_134939.zos STABLE
gpg 2.5.17 gnupg-2.5.17.20260130_021013.zos STABLE
jq 1.8.1 jq-jq-1.8.1.20250919_125054.zos STABLE
less 692 less-v692-rel.20260209_153821.zos STABLE
libpsl 1.0.0 libpsl-master.20260102_060204.zos STABLE
libssh2 1.11.1 libssh2-1.11.1.20260102_060940.zos STABLE
meta 0.8.4 meta-main.20260116_055504.zos STABLE
ncurses 6.6 ncurses-6.6.20260129_223023.zos STABLE
openssl 3.6.0 openssl-3.6.0.20260101_102819. STABLE

and

pip list

which gave

Package      Version
------------ -----------
ansible-core 2.20.3
cffi 1.14.6
cryptography 3.3.2
Jinja2 3.1.6
MarkupSafe 3.0.3
packaging 26.0
pip 26.0.1
pycparser 2.20
pysear 0.4.0
PyYAML 6.0.3
pyzfile 1.0.0.post2
resolvelib 1.2.1
six 1.16.0
tzdata 2025.3
zoautil-py 1.2.5.10

How do I allocate a Unix id on z/OS?

To use Unix services (sometimes known as USS) on z/OS, a userid needs a UserID (UID). This, as on Unix,is an integer. A user can be pre-allocated a permanent UID, or be allocated a UID when when needed. See Automatically assigning unique IDs through UNIX services.

Unique or not Unique?

It is good practice for each userid to have a unique UID. If users share the same UID,

  • The users share ownership and access to the same files.
  • If you ask for the userid associated with an id – you may get the wrong answer!

However some super users need a id of 0.

You can set this as shared with

altuser colin OMVS(UID(0)SHARED)

Instead of allocating uid(0) you can use the profile BPX.SUPERUSER resource in the FACILITY class to get the authority to do most of the tasks that require superuser authority.

  1. You can explicitly specify an id which you allocate (this means you need a list of ids and owners, so you know which ids are free).
  2. You can have z/OS do this for you. See Enabling automatic assignment of unique UNIX identities.

You can use ADDUSER COLIN OMVS(AUTOUID) which allocates an available UID.

Should I used AUTOID?

I run z/OS on a zD&T image. Every 6 months or so there is a new level of z/OS which I can download. I then need to migrate userid, datasets etc to this new system. This is different to a normal customer z/OS where you have an existing system and you migrate a new version of z/OS into it.

I have ZFS file systems for all of my user data.
On the current system my userid COLIN was automatically allocated as 0000990021. Files that I own have this id.

When I get my next system, if I allocate userid COLIN with AUTOUID, it may get a different UID say 990011. Because my userid 990011 is different to the owner of the files 990021, I may not be able to access “my” files.

I could change all of my files to have a new owner (and group), or I could ensure my userid on both systems is the same 990021. Using the same UID was much easier.

How is the range of AUTOIDs defined?

This is done with the RACF FACILITY profile BPX.NEXT.USER. On my system has has

APPLICATION DATA 990041-1000000/990020-1000000

Can I define a model profile?

You can configure OMVS to automatically give a userid a UID (if it does not have one) and define the rest of the OMVS profile using a model OMVS segment. See Steps for automatically assigning unique IDs through UNIX services.

Users need a home directory

Users need a home directory. There are several ways of doing this.

  • Give users an entry HOME(‘/u/mostusers’). Every one shares the same directory – not a good idea, because they would all share the SSH keys etc.
  • You could specify HOME(‘/u/mostusers/&racuid’) and specify the userid as part of the definition. This could be done in the model profile mentioned above. If you use this method you need to create the directory, for example as part of creating the userid.
  • Use automount. See Unix services automount is a blessing and curse. Where you define a template and the hard word is done for you. For example for each userid create a ZFS and use that.

I only use a few userids, so manually allocating the userid and the home directory was easy to do.

Note: If you use automount of a directory, such as /u/, you cannot mount other file systems in /u/; you would have to use a different directory, for example /usr/.

How do I create a load module in a PDS from Unix?

This is another of the little problems which are easy once you know the anwser.

I used the shell program to compile my program.

name=extract 

export _C89_CCMODE=1

p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
p7="-Wc,ASM,ASMLIB(//'SYS1.MACLIB') "
p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC -Wa,LIST(133),RENT"

# compile it
xlc $p1 $p7 $p8 -c $name.c -o $name.o

l1="-Wl,LIST,MAP,XREF,AC=1 "
# create an executable in the file system
/bin/xlc $name.o -o $name -V $l1 1>a
extattr +a $name

# create a load module in a PDS
/bin/xlc $name.o -o "//'COLIN.LOAD(EXTRACT)'" -V $l1 1>a

Create an executable in the file system

The first bind xlc step creates an object with name “extract” in the file system.

Specify the load module

The second bind step specified a load module in a PDS. The load module is stored in COLIN.LOAD. If you copy and paste the line, make sure you have the correct quotes ( double quote, //, single quote, dataset(member),single quote,double quote). Sometimes my pasting lost a quote.

Process assembler code

My program has some assembler code…

 asm( ASM_PREFIX 
" STORAGE RELEASE,...
:"r0", "r1" , "r15" );

It needs the options “-Wc,ASM,ASMLIB(//’SYS1.MACLIB’) ” to compile it, and specify the location of the assembler macros.

Binder parameters

The line parameters in -Wl,LIST,MAP,XREF,AC=1 are passed to the binder.

Message – wrong suffix on the source file

Without the export _C89_CCMODE=1 I got the message

FSUM3008 Specify a file with the correct suffix (.c, .i, .s, .o, .x, .p, .I, or .a), or a corresponding data set name, instead of -o ./extract.