Table GRANT is a blunt instrument: SELECT on HR.EMPLOYEE means every row and every column. DB2 for z/OS adds row and column access control (RCAC) so the same SELECT returns different rows and different column values depending on who is asking. Row rules are permissions. Column rules are masks. This page covers the DDL, how activation works, how roles and session variables feed the expressions, and what happens with functions, aggregation, views, and joins.
Row access control (RAC) attaches search conditions to a base table. When it is active, Db2 AND-s that combined condition into every SQL reference to the table, including through views. Users who “have SELECT” still only see rows their permissions allow. Write operations are checked the same way.
Column access control attaches one mask per column. The mask is a CASE expression. The query still returns the same number of rows (after row permissions). The column’s value in the final result may be the real value, a partial value, a category, or NULL.
MAC (mandatory access control) on z/OS is the older multilevel security model: a RACF SECLABEL column on the table. MLS and RAC are mutually exclusive on the same table. You can still activate column masks on a table that has a security label column. Pick RAC when the rule is “branch A staff see branch A customers.” Pick MLS when the rule is classification levels that RACF already understands.
| Statement | Purpose |
|---|---|
| CREATE PERMISSION | Define a row-access search condition on a table |
| ALTER PERMISSION | ENABLE or DISABLE an existing permission |
| DROP PERMISSION | Remove a permission object |
| CREATE MASK | Define a CASE expression returned for one column |
| ALTER MASK | ENABLE or DISABLE an existing mask |
| DROP MASK | Remove a mask object |
| ALTER TABLE … ACTIVATE / DEACTIVATE | Turn RAC or column access control on or off for the table |
SECADM creates, alters, drops, and comments on permissions and masks, and activates or deactivates RAC and column access control. SECADM does not get inherent SELECT on the table. That is deliberate: the person who writes the filter is not automatically the person who reads salaries.
If SEPARATE_SECURITY is YES, SYSADM and DBADM cannot activate RCAC. If it is NO, SYSADM can. CREATE PERMISSION / CREATE MASK additional object privileges (SELECT on referenced tables, EXECUTE on UDFs) are not required for SECADM when defining the rule—IBM documents that SECADM does not need those extra privileges just to reference objects inside the mask or permission.
1234567891011121314151617181920CREATE PERMISSION BRANCH_STAFF ON CUSTOMER FOR ROWS WHERE VERIFY_GROUP_FOR_USER(SESSION_USER, 'STAFF') = 1 AND BRANCH = ( SELECT HOME_BRANCH FROM HR.EMP_PROFILE WHERE EMP_ID = SESSION_USER ) ENFORCED FOR ALL ACCESS ENABLE; CREATE PERMISSION BRANCH_MGR ON CUSTOMER FOR ROWS WHERE VERIFY_GROUP_FOR_USER(SESSION_USER, 'MGR') = 1 ENFORCED FOR ALL ACCESS ENABLE; COMMIT; ALTER TABLE CUSTOMER ACTIVATE ROW ACCESS CONTROL;
Clauses to memorize:
Multiple enabled permissions are connected with OR. In the example, a manager matches BRANCH_MGR (all rows) even if they are not in STAFF. A staff member matches only BRANCH_STAFF. There is no error when a row is hidden; it simply is not in the result.
123ALTER PERMISSION BRANCH_STAFF DISABLE; ALTER PERMISSION BRANCH_STAFF ENABLE; DROP PERMISSION BRANCH_STAFF;
CREATE PERMISSION does not invalidate packages by itself. Activation of RAC, or enabling a permission while RAC is already on, does. Create the rules first, then activate, to avoid a storm of invalidations.
If you activate RAC with no permissions, Db2 installs a default permission that allows no SQL access. That fail-closed behavior surprises teams who expected “no rules means open.” Column access control is the opposite: activating masks with no masks defined does not hide columns; unmasked columns stay visible.
One mask per column. The CASE result must match the column’s type, nullability, length, CCSID, and distinct type.
123456789101112131415CREATE MASK SSN_MASK ON EMPLOYEE FOR COLUMN SSN RETURN CASE WHEN VERIFY_GROUP_FOR_USER(SESSION_USER, 'PAYROLL') = 1 THEN SSN WHEN VERIFY_GROUP_FOR_USER(SESSION_USER, 'MGR') = 1 THEN 'XXX-XX-' || SUBSTR(SSN, 8, 4) ELSE NULL END ENABLE; COMMIT; ALTER TABLE EMPLOYEE ACTIVATE COLUMN ACCESS CONTROL;
ALTER MASK switches ENABLE/DISABLE. DROP MASK removes the object. You cannot put a mask on XML, LOB, FIELDPROC, period, history, archive, MQT, catalog, or temporary tables—the CREATE MASK list of excluded objects is long and IBM’s SQL Reference is the checklist.
Inside a trusted connection, test the Db2 role with VERIFY_ROLE_FOR_USER. That is the right function when access is “only while wearing the TELLER role,” not “in RACF group TELLER.”
123456789101112131415CREATE MASK INCOME_MASK ON CUSTOMER FOR COLUMN INCOME RETURN CASE WHEN VERIFY_ROLE_FOR_USER(SESSION_USER, 'MGR') = 1 THEN INCOME WHEN VERIFY_ROLE_FOR_USER(SESSION_USER, 'STAFF') = 1 THEN CASE WHEN INCOME >= 200000 THEN 4 WHEN INCOME BETWEEN 100000 AND 199999 THEN 3 WHEN INCOME BETWEEN 50000 AND 99999 THEN 2 ELSE 1 END ELSE NULL END ENABLE;
SESSION_USER is the primary authorization ID. Compare it to a column (row-owner pattern), look it up in a mapping table, or use VERIFY_GROUP_FOR_USER for RACF groups / secondary IDs. CURRENT SQLID is a different register; do not assume it equals SESSION_USER.
Built-in GETVARIABLE reads session variables (including ones your application sets). A connection pool can SET a tenant id or business unit, and the mask or permission can filter on that value without creating a Db2 role per tenant. Keep the variable name stable; permissions and masks on the same table must share the same environment (CURRENT SCHEMA, PATH, encoding) recorded in SYSIBM.SYSENVIRONMENT.
1234CREATE PERMISSION TENANT_ROWS ON BILLING.INVOICE FOR ROWS WHERE TENANT_ID = GETVARIABLE('CLIENT_TENANT') ENFORCED FOR ALL ACCESS ENABLE;
A UDF used in a permission or mask must be SECURED. Defining a secure object requires the CREATE_SECURE_OBJECT privilege (SECADM-controlled when SEPARATE_SECURITY is YES). Built-ins such as VERIFY_GROUP_FOR_USER and SUBSTR are fine. Functions that are not deterministic, have external action, or MODIFIES SQL DATA are not allowed in mask CASE expressions. If the mask CASE references another RCAC table, that inner table’s access control is not cascaded into the mask evaluation—design mapping tables with that in mind.
Triggers on RCAC tables must also be secure. Unsecured triggers are a common bind-time surprise after you activate control.
Row permissions run first, so SUM and COUNT never see forbidden rows. Column masks are applied so that they do not rewrite WHERE, GROUP BY, or HAVING using the masked value for grouping logic: grouping and ordering use original values. The values you see in the select list can still be masked.
Two traps:
On INSERT/UPDATE/MERGE, Db2 uses original column values to compute new values, then checks that the mask would return the column to itself. A mask that always returns 'XXX-XX-0000' will reject updates to that column.
You do not CREATE PERMISSION ON a view. Activate RCAC on the base table. Every view, join view, and tool that SQL-accesses that table inherits the filter and masks. That is why RCAC replaced a generation of “security views” that applications had to remember to use. QMF, DSNTEP2, and the payroll COBOL program all see the same rules.
Each table in a join is filtered by its own permissions before the join predicate. If CUSTOMER hides branch B rows from you, an inner join to ORDERS looks as if those customers have no orders. An outer join can null-pad masked columns; Db2 forces a null column to mask as null so you cannot smuggle a value out through IS NULL tricks in the mask CASE.
Application designers must accept that the same SQL text returns different result sets for different users without an error. Reports that “lost” rows are often working as designed.
The table is a notebook. GRANT SELECT is permission to open the notebook. A row permission is a sticker on each page: “only the kids in classroom A may look at this page.” If your sticker does not match, the page is invisible—you are not told it exists. A column mask is a folding flap over one word on the page. Teachers see the word. Kids see “****.” The school security officer (SECADM) writes stickers and flaps but does not automatically get to read the diary. Opening the notebook in a different cover (a view) does not remove the stickers.
1. What happens if you ACTIVATE ROW ACCESS CONTROL with no permissions defined?
2. How are multiple enabled row permissions combined?
3. Are SYSADM and the table owner exempt from row and column access control?
4. What must be true of a user-defined function referenced in a permission or mask?
5. Can you enable row access control on a table that already has a security-label (MLS) column?