Whamcloud - gitweb
LU-18756 sec: add resource id check to oss and mds 08/59208/6
authorMarc Vef <mvef@whamcloud.com>
Tue, 13 May 2025 11:27:31 +0000 (13:27 +0200)
committerOleg Drokin <green@whamcloud.com>
Thu, 12 Jun 2025 06:32:50 +0000 (06:32 +0000)
This patch includes the resource id check into the relevant code paths
on the oss and mds side. It is therefore included for the following
operations.

On the MDT-side:
- open
- create (file and directory)
- unlink (file and directory)
- setattr
- setxattr
- getxattr
- rename
- link

On the OST-side and on the MDT-side for Data on MDT (DoM) files:
- write
- read
- truncate
- fallocate

Some caveats:
The resource id check is not included for MDS_GETATTR RPCs due to
functional and usability concerns. Specifically for the latter, the
"struct stat" would no longer be filled resulting in "?" when running
"ls -l", which can be misunderstood.

Also, if the check is only enabled on the OST-side, writes are only
denied for "sync"/"fsync"-type operations on a file as the check is at
the server-side. If the check is enabled on the MDT-side, write-access
is denied before the OST_WRITE RPC is sent, i.e., immediately
returning the access denied error code. If a file is still in the page
cache before the check is enabled, a client can still read the local
copy of the file, which is expected.

Sanity-sec test 75a was added to exercise the ID check for the above
cases in several disciplines further testing that access to
neighboring nodemap offset ranges work as expected.

Test-Parameters: trivial testlist=sanity-sec
Signed-off-by: Marc Vef <mvef@whamcloud.com>
Change-Id: I040ddb1b934707baa84b492337139f45b856692e
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/59208
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Reviewed-by: Sebastien Buisson <sbuisson@ddn.com>
lustre/mdt/mdt_io.c
lustre/mdt/mdt_open.c
lustre/mdt/mdt_reint.c
lustre/mdt/mdt_xattr.c
lustre/ofd/ofd_io.c
lustre/ofd/ofd_objects.c
lustre/tests/sanity-sec.sh

index 7ba9109..5a9d12b 100644 (file)
@@ -322,6 +322,7 @@ static int mdt_preprw_read(const struct lu_env *env, struct obd_export *exp,
                           struct niobuf_remote *rnb, int *nr_local,
                           struct niobuf_local *lnb)
 {
+       struct mdt_thread_info *info = mdt_th_info(env);
        struct dt_object *dob;
        int i, j, rc;
        int maxlnb = *nr_local;
@@ -348,6 +349,11 @@ static int mdt_preprw_read(const struct lu_env *env, struct obd_export *exp,
                 */
                RETURN(0);
        }
+
+       rc = mdt_check_resource_ids(info, mo);
+       if (unlikely(rc))
+               GOTO(out_sem, rc);
+
        if (lu_object_is_dying(&mo->mot_header)) {
                CDEBUG_LIMIT(level,
                             "%s: READ IO to stale obj "DFID": rc = %d\n",
@@ -382,6 +388,7 @@ static int mdt_preprw_read(const struct lu_env *env, struct obd_export *exp,
        RETURN(0);
 buf_put:
        dt_bufs_put(env, dob, lnb, *nr_local);
+out_sem:
        up_read(&mo->mot_dom_sem);
        return rc;
 }
@@ -393,6 +400,7 @@ static int mdt_preprw_write(const struct lu_env *env, struct obd_export *exp,
                            struct niobuf_remote *rnb, int *nr_local,
                            struct niobuf_local *lnb)
 {
+       struct mdt_thread_info *info = mdt_th_info(env);
        struct dt_object *dob;
        int i, j, k, rc = 0;
        int maxlnb = *nr_local;
@@ -417,6 +425,11 @@ static int mdt_preprw_write(const struct lu_env *env, struct obd_export *exp,
                /* exit with no data written, note nr_local = 0 above */
                GOTO(unlock, rc);
        }
+
+       rc = mdt_check_resource_ids(info, mo);
+       if (unlikely(rc))
+               GOTO(unlock, rc);
+
        if (lu_object_is_dying(&mo->mot_header)) {
                /* This is possible race between object destroy followed by
                 * discard BL AST and client cache flushing. Object is
@@ -1101,6 +1114,10 @@ int mdt_fallocate_hdl(struct tgt_session_info *tsi)
                GOTO(out_put, rc);
        }
 
+       rc = mdt_check_resource_ids(info, mo);
+       if (unlikely(rc))
+               GOTO(out_put, rc);
+
        la_from_obdo(la, oa, OBD_MD_FLMTIME | OBD_MD_FLATIME | OBD_MD_FLCTIME);
 
        down_write(&mo->mot_dom_sem);
@@ -1374,6 +1391,10 @@ int mdt_punch_hdl(struct tgt_session_info *tsi)
                GOTO(out_put, rc);
        }
 
+       rc = mdt_check_resource_ids(info, mo);
+       if (unlikely(rc))
+               GOTO(out_put, rc);
+
        down_write(&mo->mot_dom_sem);
        dob = mdt_obj2dt(mo);
 
index 58d3077..bf08042 100644 (file)
@@ -744,6 +744,10 @@ static int mdt_open_by_fid(struct mdt_thread_info *info, struct ldlm_reply *rep,
        if (IS_ERR(o))
                RETURN(rc = PTR_ERR(o));
 
+       rc = mdt_check_resource_ids(info, o);
+       if (unlikely(rc))
+               GOTO(out, rc);
+
        rc = mdt_check_enc(info, o);
        if (rc)
                GOTO(out, rc);
@@ -1158,8 +1162,12 @@ static int mdt_open_by_fid_lock(struct mdt_thread_info *info,
                GOTO(out, rc = -ENOENT);
        }
 
-       /* do not check enc for directory: always allow open */
+       /* do not check enc or id for directory: always allow open */
        if (!S_ISDIR(lu_object_attr(&o->mot_obj))) {
+               rc = mdt_check_resource_ids(info, o);
+               if (unlikely(rc))
+                       GOTO(out, rc);
+
                rc = mdt_check_enc(info, o);
                if (rc)
                        GOTO(out, rc);
@@ -1249,10 +1257,15 @@ static int mdt_cross_open(struct mdt_thread_info *info,
        int rc;
 
        ENTRY;
+
        o = mdt_object_find(info->mti_env, info->mti_mdt, fid);
        if (IS_ERR(o))
                RETURN(rc = PTR_ERR(o));
 
+       rc = mdt_check_resource_ids(info, o);
+       if (unlikely(rc))
+               GOTO(out, rc);
+
        rc = mdt_check_enc(info, o);
        if (rc)
                GOTO(out, rc);
@@ -1530,6 +1543,10 @@ int mdt_reint_open(struct mdt_thread_info *info, struct mdt_lock_handle *lhc)
            open_flags & MDS_OPEN_CREAT)
                GOTO(out_parent, result = -EPERM);
 
+       result = mdt_check_resource_ids(info, parent);
+       if (unlikely(result))
+               GOTO(out_parent, result);
+
        result = mdt_check_enc(info, parent);
        if (result)
                GOTO(out_parent, result);
index 6d59544..35883f9 100644 (file)
@@ -558,6 +558,10 @@ static int mdt_create(struct mdt_thread_info *info, struct mdt_lock_handle *lhc)
        if (!mdt_object_exists(parent))
                GOTO(put_parent, rc = -ENOENT);
 
+       rc = mdt_check_resource_ids(info, parent);
+       if (unlikely(rc))
+               GOTO(put_parent, rc);
+
        rc = mdt_check_enc(info, parent);
        if (rc)
                GOTO(put_parent, rc);
@@ -926,6 +930,10 @@ static int mdt_reint_setattr(struct mdt_thread_info *info,
        if (!mdt_object_exists(mo))
                GOTO(out_put, rc = -ENOENT);
 
+       rc = mdt_check_resource_ids(info, mo);
+       if (unlikely(rc))
+               GOTO(out_put, rc);
+
        if (mdt_object_remote(mo))
                GOTO(out_put, rc = -EREMOTE);
 
@@ -1288,6 +1296,10 @@ static int mdt_reint_unlink(struct mdt_thread_info *info,
                        GOTO(put_child, rc = -ENOENT);
        }
 
+       rc = mdt_check_resource_ids(info, mc);
+       if (unlikely(rc))
+               GOTO(put_child, rc);
+
        child_lh = &info->mti_lh[MDT_LH_CHILD];
        if (mdt_object_remote(mc)) {
                struct mdt_body  *repbody;
@@ -1464,6 +1476,10 @@ static int mdt_reint_link(struct mdt_thread_info *info,
                GOTO(put_source, rc = -ENOENT);
        }
 
+       rc = mdt_check_resource_ids(info, ms);
+       if (unlikely(rc))
+               GOTO(put_source, rc);
+
        CFS_RACE(OBD_FAIL_MDS_LINK_RENAME_RACE);
 
        lhp = &info->mti_lh[MDT_LH_PARENT];
@@ -1475,6 +1491,10 @@ static int mdt_reint_link(struct mdt_thread_info *info,
        if (rc)
                GOTO(unlock_parent, rc);
 
+       rc = mdt_check_resource_ids(info, mp);
+       if (unlikely(rc))
+               GOTO(unlock_parent, rc);
+
        rc = mdt_check_enc(info, mp);
        if (rc)
                GOTO(unlock_parent, rc);
@@ -2288,6 +2308,10 @@ int mdt_reint_migrate(struct mdt_thread_info *info,
        if (!S_ISDIR(lu_object_attr(&pobj->mot_obj)))
                GOTO(put_parent, rc = -ENOTDIR);
 
+       rc = mdt_check_resource_ids(info, pobj);
+       if (unlikely(rc))
+               GOTO(put_parent, rc);
+
        rc = mdt_check_enc(info, pobj);
        if (rc)
                GOTO(put_parent, rc);
@@ -2739,6 +2763,10 @@ lock_bfl:
        if (IS_ERR(msrcdir))
                RETURN(PTR_ERR(msrcdir));
 
+       rc = mdt_check_resource_ids(info, msrcdir);
+       if (unlikely(rc))
+               GOTO(out_put_srcdir, rc);
+
        rc = mdt_check_enc(info, msrcdir);
        if (rc)
                GOTO(out_put_srcdir, rc);
@@ -2755,6 +2783,11 @@ lock_bfl:
                        GOTO(out_put_srcdir, rc = PTR_ERR(mtgtdir));
        }
 
+       /* if this succeeds we do not need to check "mnew" later again */
+       rc = mdt_check_resource_ids(info, mtgtdir);
+       if (unlikely(rc))
+               GOTO(out_put_tgtdir, rc);
+
        rc = mdt_check_enc(info, mtgtdir);
        if (rc)
                GOTO(out_put_tgtdir, rc);
@@ -2878,6 +2911,10 @@ lock_bfl:
                GOTO(out_put_old, rc = -ENOENT);
        }
 
+       rc = mdt_check_resource_ids(info, mold);
+       if (unlikely(rc))
+               GOTO(out_put_old, rc);
+
        if (mdt_object_remote(mold) && !mdt->mdt_enable_remote_rename)
                GOTO(out_put_old, rc = -EXDEV);
 
index 87a5438..fced635 100644 (file)
@@ -246,6 +246,10 @@ int mdt_getxattr(struct mdt_thread_info *info)
        if (rc)
                RETURN(err_serious(rc));
 
+       rc = mdt_check_resource_ids(info, info->mti_object);
+       if (unlikely(rc))
+               RETURN(err_serious(rc));
+
        next = mdt_object_child(info->mti_object);
        easize = mdt_getxattr_pack_reply(info);
        if (easize == -ENODATA)
@@ -617,6 +621,10 @@ int mdt_reint_setxattr(struct mdt_thread_info *info,
        if (IS_ERR(obj))
                GOTO(out, rc = PTR_ERR(obj));
 
+       rc = mdt_check_resource_ids(info, obj);
+       if (unlikely(rc))
+               GOTO(out_unlock, rc);
+
        tgt_vbr_obj_set(env, mdt_obj2dt(obj));
        rc = mdt_version_get_check_save(info, obj, 0);
        if (rc)
index ca7b89a..d3938ad 100644 (file)
@@ -605,6 +605,10 @@ static int ofd_preprw_read(const struct lu_env *env, struct obd_export *exp,
        if (!ofd_object_exists(fo))
                GOTO(obj_put, rc = -ENOENT);
 
+       rc = ofd_check_resource_ids(env, exp);
+       if (unlikely(rc))
+               GOTO(obj_put, rc);
+
        if (ptlrpc_connection_is_local(exp->exp_connection))
                dbt |= DT_BUFS_TYPE_LOCAL;
 
@@ -1251,6 +1255,10 @@ ofd_commitrw_write(const struct lu_env *env, struct obd_export *exp,
        if (!ofd_object_exists(fo))
                GOTO(out, rc = -ENOENT);
 
+       rc = ofd_check_resource_ids(env, exp);
+       if (unlikely(rc))
+               GOTO(out, rc);
+
        /*
         * The first write to each object must set some attributes.  It is
         * important to set the uid/gid before calling
index 5a5a6d8..c42b050 100644 (file)
@@ -684,6 +684,11 @@ int ofd_attr_set(const struct lu_env *env, struct ofd_object *fo,
        if (!ofd_object_exists(fo))
                GOTO(out, rc = -ENOENT);
 
+       ofd_info(env)->fti_obj = fo;
+
+       rc = ofd_check_resource_ids(env, ofd_info(env)->fti_exp);
+       if (unlikely(rc))
+               GOTO(out, rc);
 
        if (la->la_valid & LA_PROJID &&
            CFS_FAIL_CHECK(OBD_FAIL_OUT_DROP_PROJID_SET))
@@ -799,6 +804,12 @@ int ofd_object_fallocate(const struct lu_env *env, struct ofd_object *fo,
        if (!ofd_object_exists(fo))
                RETURN(-ENOENT);
 
+       ofd_info(env)->fti_obj = fo;
+
+       rc = ofd_check_resource_ids(env, ofd_info(env)->fti_exp);
+       if (unlikely(rc))
+               RETURN(rc);
+
        /* VBR: version recovery check */
        rc = ofd_version_get_check(info, fo);
        if (rc != 0)
@@ -932,6 +943,12 @@ int ofd_object_punch(const struct lu_env *env, struct ofd_object *fo,
                        GOTO(out, rc);
        }
 
+       ofd_info(env)->fti_obj = fo;
+
+       rc = ofd_check_resource_ids(env, ofd_info(env)->fti_exp);
+       if (unlikely(rc))
+               GOTO(out, rc);
+
        /* VBR: version recovery check */
        rc = ofd_version_get_check(info, fo);
        if (rc)
index f6cb642..be7d518 100755 (executable)
@@ -8047,6 +8047,483 @@ test_75() {
 }
 run_test 75 "check uid/gid/projid are set on OST and MDT for various RPCs"
 
+setup_75a() {
+       # Assumes that variables from test_75a are set
+       # Setup c0 (trusted) and c1 (tenant) nodemaps used by the clients
+       nodemap_test_setup
+       trap cleanup_75a EXIT
+
+       # configure tentant nodemap
+       do_facet mgs $LCTL nodemap_set_fileset --name $nm_tenant \
+               --fileset "/$fileset_nm" || error "Setting fileset failed"
+       do_facet mgs $LCTL nodemap_add_offset --name $nm_tenant \
+               --offset $offset_start --limit $offset_limit ||
+               error "cannot set offset for $nm_tenant"
+       do_facet mgs $LCTL nodemap_modify --name $nm_tenant \
+               --property map_mode=projid ||
+               error "cannot set offset for $nm_tenant"
+
+       # configure trusted nodemap
+       do_facet mgs $LCTL nodemap_modify --name $nm_trusted \
+               --property admin --value 1 || error "Setting admin=1 failed"
+       do_facet mgs $LCTL nodemap_modify --name $nm_trusted \
+               --property trusted --value 1 || error "Setting trusted=1 failed"
+
+       wait_nm_sync $nm_trusted trusted_nodemap
+
+       # create and set ownership for fileset dir of "nm_tenant"
+       $run_as_trusted mkdir -p $fileset_subdir ||
+               error "mkdir $fileset_subdir failed"
+       $run_as_trusted chown $((offset_start+ID0)) $fileset_subdir
+
+       # remount clients for nodemap changes to take effect.
+       # This mounts the trusted nodemap (c0) and tenant nodemap (c1)
+       export FILESET=/
+       for client in "${clients_arr[@]}"; do
+               zconf_umount_clients $client $MOUNT ||
+                       error "unable to umount client ${clients_arr[0]}"
+               zconf_mount_clients $client $MOUNT $MOUNT_OPTS ||
+                       error "unable to umount client ${clients_arr[0]}"
+       done
+       unset FILESET
+       wait_ssk
+}
+
+setup_namespace_75a() {
+       # Assumes that variables from test_75a are set
+       setup_tfiles_75a() {
+               local tenant_file=$1
+               local tenant_dir=$2
+               local offset=$3
+               $run_as_trusted echo "abc" > ${fileset_subdir}/$tenant_file ||
+                       error "echo $tenant_file failed"
+               $run_as_trusted mkdir -p ${fileset_subdir}/$tenant_dir ||
+                       error "mkdir $tenant_dir failed"
+               $run_as_trusted chmod 777 ${fileset_subdir}/$tenant_file \
+                       ${fileset_subdir}/$tenant_dir ||
+                       error "chmod 777 $tenant_file and $tenant_dir failed"
+               $run_as_trusted chown \
+                       $((offset+ID0)):$((offset+ID0)) \
+                       ${fileset_subdir}/$tenant_file \
+                       ${fileset_subdir}/$tenant_dir ||
+                       error "chown $tenant_file and $tenant_dir failed"
+       }
+
+       # setup testfiles and testdirectories. *_trusted files/dirs are
+       # world-accessible, but become inaccessible once the id_check is enabled
+       $run_as_trusted echo "abc" > ${fileset_subdir}/$tfile_trusted ||
+               error "echo $tfile_trusted failed"
+       $run_as_trusted mkdir ${fileset_subdir}/$tdir_trusted ||
+               error "mkdir $tdir_trusted failed"
+       $run_as_trusted chmod 777 ${fileset_subdir}/$tdir_trusted \
+               ${fileset_subdir}/$tfile_trusted ||
+               error "chmod 777 $tdir_trusted failed"
+
+       setup_tfiles_75a $tfile_tl $tdir_tl 100000
+       setup_tfiles_75a $tfile_tenant $tdir_tenant $offset_start
+       setup_tfiles_75a $tfile_tr $tdir_tr 300000
+
+       # DoM files
+       $run_as_trusted $LFS setstripe -E 1M -L mdt \
+                ${fileset_subdir}/${tfile_trusted}_dom ||
+               error "setstripe ${tfile_trusted}_dom failed"
+       $run_as_trusted chmod 777 ${fileset_subdir}/${tfile_trusted}_dom ||
+                       error "chmod 777 ${tfile_trusted}_dom failed"
+       $run_as_trusted $LFS setstripe -E 1M -L mdt \
+                ${fileset_subdir}/${tfile_tenant}_dom ||
+               error "setstripe ${tfile_tenant}_dom failed"
+       $run_as_trusted chown \
+                       $((offset_start+ID0)):$((offset_start+ID0)) \
+                       ${fileset_subdir}/${tfile_tenant}_dom ||
+                       error "chown ${tfile_tenant}_dom failed"
+
+       # create a file used in write tests
+       $run_as_trusted echo "def" > ${fileset_subdir}/$tf_write ||
+               error "echo  $tf_write failed"
+       $run_as_trusted chown \
+               $((offset_start+ID0)):$((offset_start+ID0)) \
+               ${fileset_subdir}/$tf_write ||
+               error "chown $tf_write failed"
+}
+
+cleanup_75a() {
+       do_nodes $(all_mdts_nodes) \
+               $LCTL set_param mdt.*.enable_resource_id_check=0 ||
+                       error "disabling resource id check on MDTs failed"
+
+       do_nodes $(all_osts_nodes) \
+               $LCTL set_param obdfilter.*.enable_resource_id_check=0 ||
+                       error "disabling resource id check on OSTs failed"
+
+       nodemap_test_cleanup
+
+       for client in "${clients_arr[@]}"; do
+               zconf_umount_clients $client $MOUNT ||
+                       error "unable to umount client $client"
+               zconf_mount_clients $client $MOUNT $MOUNT_OPTS ||
+                       error "unable to umount client $client"
+       done
+       wait_ssk
+}
+
+test_75a() {
+       local offset_start=200000
+       local offset_limit=100000
+       local nm_trusted="c0"
+       local nm_tenant="c1"
+       local fileset_nm="${tdir}/${nm_tenant}_dir"
+       local fileset_subdir="${DIR}/${fileset_nm}"
+       local tfile_trusted="testfile_trusted"
+       local tfile_tenant="testfile_tenant"
+       local tdir_trusted="testdir_trusted"
+       local tdir_tenant="testdir_tenant"
+       # *_tl and *_tr files/dirs are set up such that their fs_ids are to the
+       # left and right of the tenant's offset range, respectively. This is to
+       # exercise both cases of nodemap_map_id() when mapping FS to client IDs.
+       local tfile_tl="testfile_tenant_left"
+       local tdir_tl="testdir_tenant_left"
+       local tfile_tr="testfile_tenant_right"
+       local tdir_tr="testdir_tenant_right"
+       local tf_write="testf_write"
+       local tf="testfile"
+       local client_trusted
+       local run_as_tenant
+       local out
+
+       # This test checks that the enable_resource_id_check flag works
+       # correctly by having a tenant accessing squashed files.
+       # Without this check, tenants are able to access such files
+       # that have world-accessible permissions.
+       # With the flag enabled, this is no longer possible.
+
+       # check that enable_resource_id_check flag exists
+       do_facet mds $LCTL get_param -n mdt.*.enable_resource_id_check ||
+               skip "MDS does not have the enable_resource_id_check flag"
+       do_facet ost $LCTL get_param -n obdfilter.*.enable_resource_id_check ||
+               skip "OSS does not have the enable_resource_id_check flag"
+
+       # need two clients to continue
+       (( $CLIENTCOUNT >= 2 )) || skip "need at least two clients"
+
+       if $SHARED_KEY; then
+               skip "need non-shared key for this test"
+       fi
+
+       # assign clients and helper routines
+       client_trusted=${clients_arr[0]}
+       client_tenant=${clients_arr[1]}
+       run_as_tenant="do_node $client_tenant $RUNAS_CMD -u $ID0"
+       run_as_trusted="do_node $client_trusted"
+
+       setup_75a
+
+       do_nodes $(all_mdts_nodes) \
+               $LCTL set_param mdt.*.enable_resource_id_check=0 ||
+                       error "disabling resource id check on MDTs failed"
+
+       do_nodes $(all_osts_nodes) \
+               $LCTL set_param obdfilter.*.enable_resource_id_check=0 ||
+                       error "disabling resource id check on OSTs failed"
+
+       report_client_view_75a() {
+               echo "Trusted view:"
+               $run_as_trusted ls -al $fileset_subdir
+               echo "------------------------------"
+               echo "Tenant view:"
+               $run_as_tenant ls -al $MOUNT
+       }
+
+       75a_drop_tenant_cache() {
+               do_node $client_tenant \
+                       "sync ; echo 3 > /proc/sys/vm/drop_caches"
+       }
+
+       75a_op_test() {
+               local test_cmd="$run_as_tenant $1"
+               local test_success=${2:-true}
+               if $test_success; then
+                       $test_cmd || error "$1 failed"
+               else
+                       $test_cmd && error "$1 should've failed"
+               fi
+       }
+
+       75a_read_test() {
+               local test_cmd="$run_as_tenant $1"
+               local test_success=${2:-true}
+               local expected=${3:-"def"}
+               local out
+               if $test_success; then
+                       out=$($test_cmd) || error "$1 failed"
+                       echo $out
+                       [[ $out == $expected ]] ||
+                               error "read $expected for $1 incorrect"
+               else
+                       $test_cmd && error "$1 should've failed"
+               fi
+       }
+
+       75a_getxattr_test() {
+               local test_cmd="$run_as_tenant getfattr -n user.abc $1"
+               local test_success=${2:-true}
+               local expected=${3:-"\"def\""}
+               local out
+               if $test_success; then
+                       out=$($test_cmd | awk -F'=' '/user.abc/ {print $2}') ||
+                               error "$1 failed"
+                       echo $out
+                       [[ $out == $expected ]] ||
+                               error "getxattr $expected for $1 incorrect"
+               else
+                       $test_cmd && error "$1 should've failed"
+               fi
+       }
+
+       # Setup testrun 1
+       setup_namespace_75a
+       report_client_view_75a
+       # Testrun 1 begins (check disabled)
+       # 1. write to files
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_trusted" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tl" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tenant" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tr" true
+       75a_drop_tenant_cache
+       # 2. read from files
+       75a_read_test "cat ${MOUNT}/$tfile_trusted" true
+       75a_read_test "cat ${MOUNT}/$tfile_tl" true
+       75a_read_test "cat ${MOUNT}/$tfile_tenant" true
+       75a_read_test "cat ${MOUNT}/$tfile_tr" true
+       75a_drop_tenant_cache
+       # 3. create files in various dirs
+       75a_op_test "touch ${MOUNT}/${tdir_trusted}/$tf" true
+       75a_op_test "touch ${MOUNT}/${tdir_tl}/$tf" true
+       75a_op_test "touch ${MOUNT}/${tdir_tenant}/$tf" true
+       75a_op_test "touch ${MOUNT}/${tdir_tr}/$tf" true
+       # 4. soft and hard links
+       75a_op_test "ln ${MOUNT}/$tfile_trusted \
+               ${MOUNT}/${tfile_trusted}_hlink" true
+       75a_op_test "ln -s ${MOUNT}/$tfile_trusted \
+               ${MOUNT}/${tfile_trusted}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tl \
+               ${MOUNT}/${tfile_tl}_hlink" true
+       75a_op_test "ln -s ${MOUNT}/$tfile_tl \
+               ${MOUNT}/${tfile_tl}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tenant \
+               ${MOUNT}/${tfile_tenant}_hlink" true
+       75a_op_test "ln -s ${MOUNT}/$tfile_tenant \
+               ${MOUNT}/${tfile_tenant}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tr \
+               ${MOUNT}/${tfile_tr}_hlink" true
+       75a_op_test "ln -s ${MOUNT}/$tfile_tr \
+               ${MOUNT}/${tfile_tr}_slink" true
+       75a_read_test "cat ${MOUNT}/${tfile_trusted}_hlink" true
+       75a_read_test "cat ${MOUNT}/${tfile_trusted}_slink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tl}_hlink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tl}_slink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tenant}_hlink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tenant}_slink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tr}_hlink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tr}_slink" true
+       75a_op_test "rm ${MOUNT}/*_hlink" true
+       75a_op_test "rm ${MOUNT}/*_slink" true
+       # 5. fallocate (zfs does not support pre-allocation via fallocate(2))
+       if [[ "$ost1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_trusted" true
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tl" true
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tenant" true
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tr" true
+       fi
+       # 6. truncate
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_trusted 524288" true
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tl 524288" true
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tenant 524288" true
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tr 524288" true
+       # 7. rename files (and back)
+       75a_op_test "mv ${MOUNT}/$tfile_trusted ${MOUNT}/${tfile_trusted}_" true
+       75a_op_test "mv ${MOUNT}/${tfile_trusted}_ ${MOUNT}/$tfile_trusted" true
+       75a_op_test "mv ${MOUNT}/$tfile_tl ${MOUNT}/${tfile_tl}_" true
+       75a_op_test "mv ${MOUNT}/${tfile_tl}_ ${MOUNT}/$tfile_tl" true
+       75a_op_test "mv ${MOUNT}/$tfile_tenant ${MOUNT}/${tfile_tenant}_" true
+       75a_op_test "mv ${MOUNT}/${tfile_tenant}_ ${MOUNT}/$tfile_tenant" true
+       75a_op_test "mv ${MOUNT}/$tfile_tr ${MOUNT}/${tfile_tr}_" true
+       75a_op_test "mv ${MOUNT}/${tfile_tr}_ ${MOUNT}/$tfile_tr" true
+       # 8. trigger setattr operation with "touch" (timestamp update)
+       75a_op_test "touch ${MOUNT}/$tfile_trusted" true
+       75a_op_test "touch ${MOUNT}/$tfile_tl" true
+       75a_op_test "touch ${MOUNT}/$tfile_tenant" true
+       75a_op_test "touch ${MOUNT}/$tfile_tr" true
+       # 9. xattr, set and get
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_trusted" true
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tl" true
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tenant" true
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tr" true
+       75a_getxattr_test ${MOUNT}/$tfile_trusted true
+       75a_getxattr_test ${MOUNT}/$tfile_tl true
+       75a_getxattr_test ${MOUNT}/$tfile_tenant true
+       75a_getxattr_test ${MOUNT}/$tfile_tr true
+       # 10. remove create files from tenant dirs
+       75a_op_test "rm ${MOUNT}/${tdir_trusted}/$tf" true
+       75a_op_test "rm ${MOUNT}/${tdir_tl}/$tf" true
+       75a_op_test "rm ${MOUNT}/${tdir_tenant}/$tf" true
+       75a_op_test "rm ${MOUNT}/${tdir_tr}/$tf" true
+       # 11. remove all tenant dirs
+       75a_op_test "rmdir ${MOUNT}/$tdir_trusted" true
+       75a_op_test "rmdir ${MOUNT}/$tdir_tl" true
+       75a_op_test "rmdir ${MOUNT}/$tdir_tenant" true
+       75a_op_test "rmdir ${MOUNT}/$tdir_tr" true
+       # 12. remove remaining tenant files from root
+       75a_op_test "rm ${MOUNT}/$tfile_trusted" true
+       75a_op_test "rm ${MOUNT}/$tfile_tl" true
+       75a_op_test "rm ${MOUNT}/$tfile_tenant" true
+       75a_op_test "rm ${MOUNT}/$tfile_tr" true
+       # 13. Data on MDT cases
+       if [[ "$mds1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/${tfile_trusted}_dom" true
+       fi
+       75a_op_test "$TRUNCATE ${MOUNT}/${tfile_trusted}_dom 524288" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/${tfile_trusted}_dom" true
+       75a_drop_tenant_cache
+       75a_read_test "cat ${MOUNT}/${tfile_trusted}_dom" true
+       75a_op_test "rm ${MOUNT}/${tfile_trusted}_dom" true
+
+       if [[ "$mds1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/${tfile_tenant}_dom" true
+       fi
+       75a_op_test "$TRUNCATE ${MOUNT}/${tfile_tenant}_dom 524288" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/${tfile_tenant}_dom" true
+       75a_drop_tenant_cache
+       75a_read_test "cat ${MOUNT}/${tfile_tenant}_dom" true
+       75a_op_test "rm ${MOUNT}/${tfile_tenant}_dom" true
+
+       report_client_view_75a
+
+       do_nodes $(all_mdts_nodes) \
+               $LCTL set_param mdt.*.enable_resource_id_check=1 ||
+                       error "enabling resource id check on MDTs failed"
+
+       do_nodes $(all_osts_nodes) \
+               $LCTL set_param obdfilter.*.enable_resource_id_check=1 ||
+                       error "enabling resource id check on OSTs failed"
+
+       # Setup testrun 2
+       setup_namespace_75a
+       report_client_view_75a
+
+       # Testrun 2 begins (check enabled)
+       # 1. write to files
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_trusted" false
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tl" false
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tenant" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/$tfile_tr" false
+       75a_drop_tenant_cache
+       # 2. read from files
+       75a_read_test "cat ${MOUNT}/$tfile_trusted" false
+       75a_read_test "cat ${MOUNT}/$tfile_tl" false
+       75a_read_test "cat ${MOUNT}/$tfile_tenant" true
+       75a_read_test "cat ${MOUNT}/$tfile_tr" false
+       75a_drop_tenant_cache
+       # 3. create files
+       75a_op_test "touch ${MOUNT}/${tdir_trusted}/$tf" false
+       75a_op_test "touch ${MOUNT}/${tdir_tl}/$tf" false
+       75a_op_test "touch ${MOUNT}/${tdir_tenant}/$tf" true
+       75a_op_test "touch ${MOUNT}/${tdir_tr}/$tf" false
+       # 4. soft and hard links (cannot create hard links but soft links)
+       75a_op_test "ln ${MOUNT}/$tfile_trusted \
+               ${MOUNT}/${tfile_trusted}_hlink" false
+       75a_op_test "ln -s ${MOUNT}/$tfile_trusted \
+               ${MOUNT}/${tfile_trusted}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tl \
+               ${MOUNT}/${tfile_tl}_hlink" false
+       75a_op_test "ln -s ${MOUNT}/$tfile_tl \
+               ${MOUNT}/${tfile_tl}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tenant \
+               ${MOUNT}/${tfile_tenant}_hlink" true
+       75a_op_test "ln -s ${MOUNT}/$tfile_tenant \
+               ${MOUNT}/${tfile_tenant}_slink" true
+       75a_op_test "ln ${MOUNT}/$tfile_tr \
+               ${MOUNT}/${tfile_tr}_hlink" false
+       75a_op_test "ln -s ${MOUNT}/$tfile_tr \
+               ${MOUNT}/${tfile_tr}_slink" true
+       # can only read soft-links pointing to permitted files
+       75a_read_test "cat ${MOUNT}/${tfile_trusted}_slink" false
+       75a_read_test "cat ${MOUNT}/${tfile_tl}_slink" false
+       75a_read_test "cat ${MOUNT}/${tfile_tenant}_slink" true
+       75a_read_test "cat ${MOUNT}/${tfile_tr}_slink" false
+       # can remove all links created by tenant
+       75a_op_test "rm ${MOUNT}/${tfile_trusted}_slink" true
+       75a_op_test "rm ${MOUNT}/${tfile_tl}_slink" true
+       75a_op_test "rm ${MOUNT}/${tfile_tenant}_slink" true
+       75a_op_test "rm ${MOUNT}/${tfile_tr}_slink" true
+       75a_op_test "rm ${MOUNT}/${tfile_tenant}_hlink" true
+       # 5. fallocate (only on ldiskfs, zfs does not support pre-allocation)
+       if [[ "$ost1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_trusted" false
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tl" false
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tenant" true
+               75a_op_test "fallocate -l 1M ${MOUNT}/$tfile_tr" false
+       fi
+       # 6. truncate
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_trusted 524288" false
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tl 524288" false
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tenant 524288" true
+       75a_op_test "$TRUNCATE ${MOUNT}/$tfile_tr 524288" false
+       # 7. rename files (and back)
+       75a_op_test "mv ${MOUNT}/$tfile_trusted \
+               ${MOUNT}/${tfile_trusted}_" false
+       75a_op_test "mv ${MOUNT}/$tfile_tl ${MOUNT}/${tfile_tl}_" false
+       75a_op_test "mv ${MOUNT}/$tfile_tenant ${MOUNT}/${tfile_tenant}_" true
+       75a_op_test "mv ${MOUNT}/${tfile_tenant}_ ${MOUNT}/$tfile_tenant" true
+       75a_op_test "mv ${MOUNT}/$tfile_tr ${MOUNT}/${tfile_tr}_" false
+       # 8. trigger setattr operation with "touch" (timestamp update)
+       75a_op_test "touch ${MOUNT}/$tfile_trusted" false
+       75a_op_test "touch ${MOUNT}/$tfile_tl" false
+       75a_op_test "touch ${MOUNT}/$tfile_tenant" true
+       75a_op_test "touch ${MOUNT}/$tfile_tr" false
+       # 9. xattr, set and get
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_trusted" false
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tl" false
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tenant" true
+       75a_op_test "setfattr -n user.abc -v def ${MOUNT}/$tfile_tr" false
+       75a_getxattr_test ${MOUNT}/$tfile_trusted false
+       75a_getxattr_test ${MOUNT}/$tfile_tl false
+       75a_getxattr_test ${MOUNT}/$tfile_tenant true
+       75a_getxattr_test ${MOUNT}/$tfile_tr false
+       # 10. remove create files from tenant dirs
+       75a_op_test "rm ${MOUNT}/${tdir_tenant}/$tf" true
+       # 11. attempt to remove all tenant dirs
+       75a_op_test "rmdir ${MOUNT}/$tdir_trusted" false
+       75a_op_test "rmdir ${MOUNT}/$tdir_tl" false
+       75a_op_test "rmdir ${MOUNT}/$tdir_tenant" true
+       75a_op_test "rmdir ${MOUNT}/$tdir_tr" false
+       # 12. attempt to remove remaining tenant files from root
+       75a_op_test "rm ${MOUNT}/$tfile_trusted" false
+       75a_op_test "rm ${MOUNT}/$tfile_tl" false
+       75a_op_test "rm ${MOUNT}/$tfile_tenant" true
+       75a_op_test "rm ${MOUNT}/$tfile_tr" false
+       # 13. Data on MDT cases
+       if [[ "$mds1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/${tfile_trusted}_dom" \
+                       false
+       fi
+       75a_op_test "$TRUNCATE ${MOUNT}/${tfile_trusted}_dom 524288" false
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/${tfile_trusted}_dom" false
+       75a_read_test "cat ${MOUNT}/${tfile_trusted}_dom" false
+       75a_op_test "rm ${MOUNT}/${tfile_trusted}_dom" false
+
+       if [[ "$mds1_FSTYPE" == "ldiskfs" ]]; then
+               75a_op_test "fallocate -l 1M ${MOUNT}/${tfile_tenant}_dom" true
+       fi
+       75a_op_test "$TRUNCATE ${MOUNT}/${tfile_tenant}_dom 524288" true
+       75a_op_test "cp ${MOUNT}/$tf_write ${MOUNT}/${tfile_tenant}_dom" true
+       75a_drop_tenant_cache
+       75a_read_test "cat ${MOUNT}/${tfile_tenant}_dom" true
+       75a_op_test "rm ${MOUNT}/${tfile_tenant}_dom" true
+
+       report_client_view_75a
+}
+run_test 75a "test resource fs IDs against nodemap offset"
+
 cleanup_76() {
        # unmount client
        if is_mounted $MOUNT; then