Blob Blame History Raw
commit 549241cec9404bd211a580454fdd28cb72dfe520
Author: Gabriel Becker <ggasparb@redhat.com>
Date:   Thu Feb 24 17:24:17 2022 +0100

    Manual edited patch scap-security-guide-0.1.59-BZ1884687-PR_7770.patch.

diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/ansible/shared.yml b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/ansible/shared.yml
new file mode 100644
index 0000000..09d1984
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/ansible/shared.yml
@@ -0,0 +1,31 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+- name: Get all local users from /etc/passwd
+  ansible.builtin.getent:
+    database: passwd
+    split: ':'
+
+- name: Create local_users variable from the getent output
+  ansible.builtin.set_fact:
+    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
+
+- name: Test for existence of home directories to avoid creating them, but only fixing group ownership
+  ansible.builtin.stat:
+    path: '{{ item.value[4] }}'
+  register: path_exists
+  loop: '{{ local_users }}'
+  when:
+    - item.value[2]|int >= {{{ gid_min }}}
+    - item.value[2]|int != 65534
+
+- name: Ensure interactive local users are the group-owners of their respective home directories
+  ansible.builtin.file:
+    path: '{{ item.0.value[4] }}'
+    group: '{{ item.0.value[2] }}'
+  loop: '{{ local_users|zip(path_exists.results)|list }}'
+  when:
+    - item.1.stat is defined and item.1.stat.exists
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/bash/shared.sh
new file mode 100644
index 0000000..08f7307
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/bash/shared.sh
@@ -0,0 +1,7 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+awk -F':' '{ if ($4 >= {{{ gid_min }}} && $4 != 65534) system("chgrp -f " $4" "$6) }' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/oval/shared.xml b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/oval/shared.xml
new file mode 100644
index 0000000..a1d1f2e
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/oval/shared.xml
@@ -0,0 +1,89 @@
+<def-group>
+  <definition class="compliance" id="{{{ rule_id }}}" version="1">
+    {{{ oval_metadata("All interactive user's Home Directories must be group-owned by its user") }}}
+    <criteria operator="AND">
+      <criterion test_ref="test_file_groupownership_home_directories"
+                 comment="All interactive user's Home Directories must be group-owned by its user"/>
+      <criterion test_ref="test_file_groupownership_home_directories_duplicated"
+                 comment="Interactive users should group-own only one Home Directory"/>
+    </criteria>
+  </definition>
+
+  <!-- For detailed comments about logic used in this OVAL, check the
+       "file_ownership_home_directories" rule. -->
+  <unix:password_object id="object_file_groupownership_home_directories_objects" version="1">
+    <unix:username datatype="string" operation="not equal">nobody</unix:username>
+    <filter action="include">state_file_groupownership_home_directories_interactive_gids</filter>
+  </unix:password_object>
+
+  <unix:password_state id="state_file_groupownership_home_directories_interactive_gids" version="1">
+    <unix:group_id datatype="int" operation="greater than or equal">{{{ gid_min }}}</unix:group_id>
+  </unix:password_state>
+
+  <!-- #### prepare for test_file_groupownership_home_directories #### -->
+  <local_variable id="var_file_groupownership_home_directories_dirs" datatype="string" version="1"
+                  comment="Variable including all home dirs from primary interactive groups">
+    <object_component item_field="home_dir" object_ref="object_file_groupownership_home_directories_objects"/>
+  </local_variable>
+
+  <local_variable id="var_file_groupownership_home_directories_gids" datatype="int" version="1"
+                  comment="Variable including all gids from primary interactive group">
+    <object_component item_field="group_id" object_ref="object_file_groupownership_home_directories_objects"/>
+  </local_variable>
+
+  <!-- #### creation of object #### -->
+  <unix:file_object id="object_file_groupownership_home_directories_dirs" version="1">
+    <unix:path var_ref="var_file_groupownership_home_directories_dirs" var_check="at least one"/>
+    <unix:filename xsi:nil="true"/>
+  </unix:file_object>
+
+  <!-- #### creation of state #### -->
+  <unix:file_state id="state_file_groupownership_home_directories_gids" version="1">
+    <unix:group_id datatype="int" var_check="only one" var_ref="var_file_groupownership_home_directories_gids"/>
+  </unix:file_state>
+
+  <!-- #### creation of test #### -->
+  <!-- #### creatin of test_file_groupownership_home_directories #### -->
+  <unix:file_test id="test_file_groupownership_home_directories" check="all" check_existence="any_exist"
+                  version="1" comment="All home directories are group-owned by a local interactive group">
+    <unix:object object_ref="object_file_groupownership_home_directories_dirs"/>
+    <unix:state state_ref="state_file_groupownership_home_directories_gids"/>
+  </unix:file_test>
+
+  <!-- #### prepare for test_file_groupownership_home_directories_duplicated #### -->
+  <local_variable id="var_file_groupownership_home_directories_gids_count" datatype="int" version="1"
+                  comment="Variable including count of gids from interactive group-owners">
+    <count>
+      <object_component item_field="group_id" object_ref="object_file_groupownership_home_directories_dirs"/>
+    </count>
+  </local_variable>
+
+  <local_variable id="var_file_groupownership_home_directories_gids_count_uniq" datatype="int" version="1"
+                  comment="Variable including count of uniq gids from interactive group-owners">
+    <count>
+      <unique>
+        <object_component item_field="group_id" object_ref="object_file_groupownership_home_directories_dirs"/>
+      </unique>
+    </count>
+  </local_variable>
+
+  <!-- #### creation of object #### -->
+  <ind:variable_object id="object_file_groupownership_home_directories_gids_count" version="1">
+    <ind:var_ref>var_file_groupownership_home_directories_gids_count</ind:var_ref>
+  </ind:variable_object>
+
+  <!-- #### creation of state #### -->
+  <!-- #### creation of state_no_duplicate_groupowners #### -->
+  <ind:variable_state id="state_file_groupownership_home_directories_gids_count_uniq" version="1">
+    <ind:value datatype="int" operation="equals" var_check="at least one"
+               var_ref="var_file_groupownership_home_directories_gids_count_uniq"/>
+  </ind:variable_state>
+
+  <!-- #### creation of test #### -->
+  <ind:variable_test id="test_file_groupownership_home_directories_duplicated" check="all"
+                     check_existence="any_exist" version="1"
+                     comment="It should not exist duplicated group-owners of home dirs">
+    <ind:object object_ref="object_file_groupownership_home_directories_gids_count"/>
+    <ind:state state_ref="state_file_groupownership_home_directories_gids_count_uniq"/>
+  </ind:variable_test>
+</def-group>
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/rule.yml b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/rule.yml
index 2e6ce60..e33660f 100644
--- a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/rule.yml
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/rule.yml
@@ -10,6 +10,10 @@ description: |-
     interactive users home directory, use the following command:
     <pre>$ sudo chgrp <i>USER_GROUP</i> /home/<i>USER</i></pre>
 
+    This rule ensures every home directory related to an interactive user is
+    group-owned by an interactive user. It also ensures that interactive users
+    are group-owners of one and only one home directory.
+
 rationale: |-
     If the Group Identifier (GID) of a local interactive users home directory is
     not the same as the primary GID of the user, this would allow unauthorized
@@ -42,3 +46,9 @@ ocil: |-
     To verify the assigned home directory of all interactive users is group-
     owned by that users primary GID, run the following command:
     <pre># ls -ld $(awk -F: '($3&gt;=1000)&amp;&amp;($7 !~ /nologin/){print $6}' /etc/passwd)</pre>
+
+warnings:
+    - general: |-
+       Due to OVAL limitation, this rule can report a false negative in a
+       specific situation where two interactive users swap the group-ownership
+       of their respective home directories.
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/expected_groupowner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/expected_groupowner.pass.sh
new file mode 100644
index 0000000..1605339
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/expected_groupowner.pass.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chgrp $USER /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_all_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_all_absent.pass.sh
new file mode 100644
index 0000000..af24025
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_all_absent.pass.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -M $USER
+# This make sure home dirs related to test environment users are also removed.
+rm -Rf /home/*
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_one_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_one_absent.pass.sh
new file mode 100644
index 0000000..5bce517
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_one_absent.pass.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -M $USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_with_same_groupowner.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_with_same_groupowner.fail.sh
new file mode 100644
index 0000000..9d0f765
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/home_dirs_with_same_groupowner.fail.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Define the same owner for two home directories
+chgrp $USER1 /home/$USER1
+chgrp $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/interactive_users_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/interactive_users_absent.pass.sh
new file mode 100644
index 0000000..ed34f09
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/interactive_users_absent.pass.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# remove all interactive users (ID >= 1000) from /etc/passwd
+sed -i '/.*:[0-9]\{4,\}:/d' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_system_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_system_id.fail.sh
new file mode 100644
index 0000000..c1a87c1
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_system_id.fail.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chgrp 2 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_unknown_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_unknown_id.fail.sh
new file mode 100644
index 0000000..d352011
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/unexpected_groupowner_unknown_id.fail.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chgrp 10005 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_crossed_groupowner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_crossed_groupowner.pass.sh
new file mode 100644
index 0000000..0cffa4a
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_crossed_groupowner.pass.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Define the same owner for two home directories
+chgrp $USER2 /home/$USER1
+chgrp $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_swapped_groupowner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_swapped_groupowner.pass.sh
new file mode 100644
index 0000000..3e5b778
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_groupownership_home_directories/tests/warning_home_dirs_swapped_groupowner.pass.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Swap the group-ownership of two home directories
+# WARNING: This test scenario will report a false negative, as explained in the
+# warning section of this rule.
+chgrp $USER2 /home/$USER1
+chgrp $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/ansible/shared.yml b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/ansible/shared.yml
new file mode 100644
index 0000000..97d4274
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/ansible/shared.yml
@@ -0,0 +1,31 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+- name: Get all local users from /etc/passwd
+  ansible.builtin.getent:
+    database: passwd
+    split: ':'
+
+- name: Create local_users variable from the getent output
+  ansible.builtin.set_fact:
+    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
+
+- name: Test for existence home directories to avoid creating them, but only fixing ownership
+  ansible.builtin.stat:
+    path: '{{ item.value[4] }}'
+  register: path_exists
+  loop: '{{ local_users }}'
+  when:
+    - item.value[1]|int >= {{{ uid_min }}}
+    - item.value[1]|int != 65534
+
+- name: Ensure interactive local users are the owners of their respective home directories
+  ansible.builtin.file:
+    path: '{{ item.0.value[4] }}'
+    owner: '{{ item.0.value[1] }}'
+  loop: '{{ local_users|zip(path_exists.results)|list }}'
+  when:
+    - item.1.stat is defined and item.1.stat.exists
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/bash/shared.sh
new file mode 100644
index 0000000..1d1e675
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/bash/shared.sh
@@ -0,0 +1,7 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+awk -F':' '{ if ($3 >= {{{ uid_min }}} && $3 != 65534) system("chown -f " $3" "$6) }' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/oval/shared.xml b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/oval/shared.xml
new file mode 100644
index 0000000..3d0b9ae
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/oval/shared.xml
@@ -0,0 +1,142 @@
+<def-group>
+  <!-- Updated references of the OVAL language used in this file can be found in this link: 
+       https://oval-community-guidelines.readthedocs.io/en/latest/oval-schema-documentation/oval-definitions-schema.html
+  -->
+
+  <definition class="compliance" id="{{{ rule_id }}}" version="1">
+    {{{ oval_metadata("All interactive user's Home Directories must be owned by its user") }}}
+    <criteria operator="AND">
+      <criterion test_ref="test_file_ownership_home_directories"
+                 comment="All interactive user's Home Directories must be owned by its user"/>
+      <criterion test_ref="test_file_ownership_home_directories_duplicated"
+                 comment="Interactive users should own only one Home Directory"/>
+    </criteria>
+  </definition>
+
+  <!-- 
+    Extract a list composed of password objects filtered by UIDs starting in {{{ uid_min }}} and
+    not equal to "nobody". Most of (if not all) distros have the special user "nobody" with uid
+    65354. Despite it be technically classified as an interactive user, it is a special case with
+    very limited access. So, we ignore it. The resulted password object will be further used to
+    create local variables composed by UIDs e Home Dirs.
+  -->
+  <unix:password_object id="object_file_ownership_home_directories_objects" version="1">
+    <unix:username datatype="string" operation="not equal">nobody</unix:username>
+    <filter action="include">state_file_ownership_home_directories_interactive_uids</filter>
+  </unix:password_object>
+
+  <!--
+    In distros which uses PAM (almost all), by default, the uid of interactive users and groups
+    starts at 1000. We use this information to make sure this password_state object will be
+    composed only with objects related to interactive users.
+  -->
+  <unix:password_state id="state_file_ownership_home_directories_interactive_uids" version="1">
+    <unix:user_id datatype="int" operation="greater than or equal">{{{ uid_min }}}</unix:user_id>
+  </unix:password_state>
+
+  <!-- 
+    #### prepare for test_file_groupownership_home_directories ####
+    From the list of interactive users objects we create a local variable composed of their home dirs.
+  -->
+  <local_variable id="var_file_ownership_home_directories_dirs" datatype="string" version="1"
+                  comment="Variable including all home dirs from interactive users">
+    <object_component item_field="home_dir" object_ref="object_file_ownership_home_directories_objects"/>
+  </local_variable>
+
+  <!-- 
+    From the list of interactive users objects we create a local variable composed of their uids.
+  -->
+  <local_variable id="var_file_ownership_home_directories_uids" datatype="int" version="1"
+                  comment="List of interactive users uids">
+    <object_component item_field="user_id" object_ref="object_file_ownership_home_directories_objects"/>
+  </local_variable>
+
+  <!-- 
+    #### creation of object ####
+    We have the home dirs, but to test their ownership we need a "file_object" and not a password
+    object, as the initial source of this information is. So, we create this file_object based on
+    content from the previous local variable.
+  -->
+  <unix:file_object id="object_file_ownership_home_directories_dirs" version="1">
+    <unix:path var_ref="var_file_ownership_home_directories_dirs" var_check="at least one"/>
+    <unix:filename xsi:nil="true"/>
+  </unix:file_object>
+
+  <!--
+    #### creation of state ####
+    We have the relevant uids, but we need a "file_state" object to use in our intendend test.
+    So, we create this file_state based on content from the previous local variable.
+  -->
+  <unix:file_state id="state_file_ownership_home_directories_uids" version="1">
+    <unix:user_id datatype="int" var_check="only one" var_ref="var_file_ownership_home_directories_uids"/>
+  </unix:file_state>
+
+  <!--
+    #### creation of test ####
+    Perform the test to ensure that all home dirs are owned by an interactive user. 
+    This test will make sure that no foreign or system user is owner of an existing home dir.
+    However, this can't ensure that one local interactive user is the owner of only one home dir.
+    Currently this is an OVAL limitation which we try to mitigate with a second test below.
+  -->
+  <unix:file_test id="test_file_ownership_home_directories" check="all" check_existence="any_exist"
+                  version="1" comment="All home directories are owned by a local interactive user">
+    <unix:object object_ref="object_file_ownership_home_directories_dirs"/>
+    <unix:state state_ref="state_file_ownership_home_directories_uids"/>
+  </unix:file_test>
+
+  <!--
+    We create an extra test to make sure that the number of home dirs and their respective owners
+    are the same. This is to catch situations where one local user owns more than one home dir.
+    However, we still can have a situation where two local users cross the ownership of their
+    respective home dirs. Although very atypical, we should be aware of this possible false
+    positive and that it is not possible to be solved with the current OVAL capabilities.
+  -->
+  <!--
+    This create an int variable composed by the count of file_object items.
+  -->
+  <local_variable id="var_file_ownership_home_directories_uids_count" datatype="int" version="1"
+                  comment="Count home dirs related to interactive users">
+    <count>
+      <object_component item_field="user_id" object_ref="object_file_ownership_home_directories_dirs"/>
+    </count>
+  </local_variable>
+
+  <!--
+    This create an int variable composed by the count of unique user_ids collected from
+    file_object items.
+  -->
+  <local_variable id="var_file_ownership_home_directories_uids_count_uniq" datatype="int" version="1"
+                  comment="Count current owners of relevant home dirs">
+    <count>
+      <unique>
+        <object_component item_field="user_id" object_ref="object_file_ownership_home_directories_dirs"/>
+      </unique>
+    </count>
+  </local_variable>
+
+  <!--
+    #### creation of object ####
+    Turn the OVAL variable representing count of home dirs into OVAL object.
+    This way we can test it further.
+  -->
+  <ind:variable_object id="object_file_ownership_home_directories_uids_count" version="1">
+    <ind:var_ref>var_file_ownership_home_directories_uids_count</ind:var_ref>
+  </ind:variable_object>
+
+  <!--
+    #### creation of state ####
+    this state checks that both counts (unique and non-unique) are the same
+  -->
+  <ind:variable_state id="state_file_ownership_home_directories_uids_count_uniq" version="1">
+    <ind:value datatype="int" operation="equals" var_check="at least one"
+               var_ref="var_file_ownership_home_directories_uids_count_uniq"/>
+  </ind:variable_state>
+
+  <!-- #### creation of test #### -->
+  <ind:variable_test id="test_file_ownership_home_directories_duplicated" check="all" 
+                     check_existence="any_exist" version="1"
+                     comment="It should not exist duplicated owners of home dirs">
+    <ind:object object_ref="object_file_ownership_home_directories_uids_count"/>
+    <ind:state state_ref="state_file_ownership_home_directories_uids_count_uniq"/>
+  </ind:variable_test>
+</def-group>
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/rule.yml b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/rule.yml
index 198a9be..042f484 100644
--- a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/rule.yml
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/rule.yml
@@ -10,6 +10,10 @@ description: |-
     the following command:
     <pre>$ sudo chown <i>USER</i> /home/<i>USER</i></pre>
 
+    This rule ensures every home directory related to an interactive user is
+    owned by an interactive user. It also ensures that interactive users are
+    owners of one and only one home directory.
+
 rationale: |-
     If a local interactive user does not own their home directory, unauthorized
     users could access or modify the user's files, and the users may not be able to
@@ -31,3 +35,9 @@ ocil_clause: 'the user ownership is incorrect'
 ocil: |-
     To verify the home directory ownership, run the following command:
     <pre># ls -ld $(awk -F: '($3&gt;=1000)&amp;&amp;($7 !~ /nologin/){print $6}' /etc/passwd)</pre>
+
+warnings:
+    - general: |-
+       Due to OVAL limitation, this rule can report a false negative in a
+       specific situation where two interactive users swap the ownership of
+       their respective home directories.
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/expected_owner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/expected_owner.pass.sh
new file mode 100644
index 0000000..585f759
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/expected_owner.pass.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chown $USER /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dir_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dir_absent.pass.sh
new file mode 100644
index 0000000..7c181af
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dir_absent.pass.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -M $USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_all_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_all_absent.pass.sh
new file mode 100644
index 0000000..af24025
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_all_absent.pass.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -M $USER
+# This make sure home dirs related to test environment users are also removed.
+rm -Rf /home/*
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_one_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_one_absent.pass.sh
new file mode 100644
index 0000000..5bce517
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_one_absent.pass.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -M $USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_with_same_owner.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_with_same_owner.fail.sh
new file mode 100644
index 0000000..e6aef9e
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/home_dirs_with_same_owner.fail.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Define the same owner for two home directories
+chown $USER1 /home/$USER1
+chown $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/interactive_users_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/interactive_users_absent.pass.sh
new file mode 100644
index 0000000..ed34f09
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/interactive_users_absent.pass.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# remove all interactive users (ID >= 1000) from /etc/passwd
+sed -i '/.*:[0-9]\{4,\}:/d' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_system_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_system_id.fail.sh
new file mode 100644
index 0000000..011b315
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_system_id.fail.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chown 2 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_unknown_id.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_unknown_id.fail.sh
new file mode 100644
index 0000000..733af78
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/unexpected_owner_unknown_id.fail.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chown 10005 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_crossed_owner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_crossed_owner.pass.sh
new file mode 100644
index 0000000..df5655f
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_crossed_owner.pass.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Define the same owner for two home directories
+chown $USER2 /home/$USER1
+chown $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_swapped_owner.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_swapped_owner.pass.sh
new file mode 100644
index 0000000..e9cfd5b
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_ownership_home_directories/tests/warning_home_dirs_swapped_owner.pass.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+USER1="cac_user1"
+USER2="cac_user2"
+
+useradd -m $USER1
+useradd -m $USER2
+# Swap the ownership of two home directories
+# WARNING: This test scenario will report a false negative, as explained in the
+# warning section of this rule.
+chown $USER2 /home/$USER1
+chown $USER1 /home/$USER2
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/ansible/shared.yml b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/ansible/shared.yml
new file mode 100644
index 0000000..945ed7e
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/ansible/shared.yml
@@ -0,0 +1,31 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+- name: Get all local users from /etc/passwd
+  ansible.builtin.getent:
+    database: passwd
+    split: ':'
+
+- name: Create local_users variable from the getent output
+  ansible.builtin.set_fact:
+    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
+
+- name: Test for existence home directories to avoid creating them, but only fixing group ownership
+  ansible.builtin.stat:
+    path: '{{ item.value[4] }}'
+  register: path_exists
+  loop: '{{ local_users }}'
+  when:
+    - item.value[2]|int >= {{{ uid_min }}}
+    - item.value[2]|int != 65534
+
+- name: Ensure interactive local users are the group-owners of their respective home directories
+  ansible.builtin.file:
+    path: '{{ item.0.value[4] }}'
+    mode: '0700'
+  loop: '{{ local_users|zip(path_exists.results)|list }}'
+  when:
+    - item.1.stat is defined and item.1.stat.exists
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/bash/shared.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/bash/shared.sh
new file mode 100644
index 0000000..4ebc674
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/bash/shared.sh
@@ -0,0 +1,7 @@
+# platform = multi_platform_all
+# reboot = false
+# strategy = restrict
+# complexity = low
+# disruption = low
+
+awk -F':' '{ if ($4 >= {{{ uid_min }}} && $4 != 65534) system("chmod -f 700 "$6) }' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/oval/shared.xml b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/oval/shared.xml
new file mode 100644
index 0000000..0cb261e
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/oval/shared.xml
@@ -0,0 +1,51 @@
+<def-group>
+  <definition class="compliance" id="{{{ rule_id }}}" version="1">
+    {{{ oval_metadata("All Interactive User Home Directories Must Have mode 0750 Or Less Permissive") }}}
+    <criteria>
+      <criterion test_ref="test_file_permissions_home_directories"
+                 comment="All interactive user's Home Directories must have proper permissions"/>
+    </criteria>
+  </definition>
+
+  <!-- For detailed comments about logic used in this OVAL, check the
+       "file_ownership_home_directories" rule. -->
+  <unix:password_object id="object_file_permissions_home_directories_objects" version="1">
+    <unix:username datatype="string" operation="not equal">nobody</unix:username>
+    <filter action="include">state_file_permissions_home_directories_interactive_uids</filter>
+  </unix:password_object>
+
+  <unix:password_state id="state_file_permissions_home_directories_interactive_uids" version="1">
+    <unix:user_id datatype="int" operation="greater than or equal">{{{ uid_min }}}</unix:user_id>
+  </unix:password_state>
+
+  <!-- #### prepare for test_file_permissions_home_directories #### -->
+  <local_variable id="var_file_permissions_home_directories_dirs" datatype="string" version="1"
+                  comment="Variable including all home dirs from interactive users">
+    <object_component item_field="home_dir" object_ref="object_file_permissions_home_directories_objects"/>
+  </local_variable>
+
+  <!-- #### creation of object #### -->
+  <unix:file_object id="object_file_permissions_home_directories_dirs" version="1">
+    <unix:path var_ref="var_file_permissions_home_directories_dirs" var_check="at least one"/>
+    <unix:filename xsi:nil="true"/>
+  </unix:file_object>
+
+  <!-- #### creation of state #### -->
+  <unix:file_state id="state_file_permissions_home_directories_dirs" version="1" operator='AND'>
+    <unix:type operation="equals">directory</unix:type>
+    <unix:suid datatype="boolean">false</unix:suid>
+    <unix:sgid datatype="boolean">false</unix:sgid>
+    <unix:sticky datatype="boolean">false</unix:sticky>
+    <unix:gwrite datatype="boolean">false</unix:gwrite>
+    <unix:oread datatype="boolean">false</unix:oread>
+    <unix:owrite datatype="boolean">false</unix:owrite>
+    <unix:oexec datatype="boolean">false</unix:oexec>
+  </unix:file_state>
+
+  <!-- #### creation of test #### -->
+  <unix:file_test id="test_file_permissions_home_directories" check="all" check_existence="any_exist"
+                  version="1" comment="All home directories have proper permissions">
+    <unix:object object_ref="object_file_permissions_home_directories_dirs"/>
+    <unix:state state_ref="state_file_permissions_home_directories_dirs"/>
+  </unix:file_test>
+</def-group>
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/acceptable_permission.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/acceptable_permission.pass.sh
new file mode 100644
index 0000000..aaf939e
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/acceptable_permission.pass.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chmod 750 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/expected_permissions.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/expected_permissions.pass.sh
new file mode 100644
index 0000000..5dfd426
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/expected_permissions.pass.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chmod 700 /home/$USER
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/home_dirs_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/home_dirs_absent.pass.sh
new file mode 100644
index 0000000..af24025
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/home_dirs_absent.pass.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -M $USER
+# This make sure home dirs related to test environment users are also removed.
+rm -Rf /home/*
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/interactive_users_absent.pass.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/interactive_users_absent.pass.sh
new file mode 100644
index 0000000..ed34f09
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/interactive_users_absent.pass.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+# remove all interactive users (ID >= 1000) from /etc/passwd
+sed -i '/.*:[0-9]\{4,\}:/d' /etc/passwd
diff --git a/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/lenient_permission.fail.sh b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/lenient_permission.fail.sh
new file mode 100644
index 0000000..2f337d2
--- /dev/null
+++ b/linux_os/guide/system/accounts/accounts-session/file_permissions_home_directories/tests/lenient_permission.fail.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+USER="cac_user"
+useradd -m $USER
+chmod 755 /home/$USER
diff --git a/ssg/constants.py b/ssg/constants.py
index e2d3077..64e2712 100644
--- a/ssg/constants.py
+++ b/ssg/constants.py
@@ -380,6 +380,7 @@ MAKEFILE_ID_TO_PRODUCT_MAP = {
 
 
 # Application constants
+DEFAULT_GID_MIN = 1000
 DEFAULT_UID_MIN = 1000
 DEFAULT_GRUB2_BOOT_PATH = '/boot/grub2'
 DEFAULT_DCONF_GDM_DIR = 'gdm.d'
diff --git a/ssg/products.py b/ssg/products.py
index 25178b7..e410e06 100644
--- a/ssg/products.py
+++ b/ssg/products.py
@@ -7,6 +7,7 @@ from glob import glob
 
 from .build_cpe import ProductCPEs
 from .constants import (product_directories,
+                        DEFAULT_GID_MIN,
                         DEFAULT_UID_MIN,
                         DEFAULT_GRUB2_BOOT_PATH,
                         DEFAULT_DCONF_GDM_DIR,
@@ -39,6 +40,9 @@ def _get_implied_properties(existing_properties):
             if pkg_manager in PKG_MANAGER_TO_CONFIG_FILE:
                 result["pkg_manager_config_file"] = PKG_MANAGER_TO_CONFIG_FILE[pkg_manager]
 
+    if "gid_min" not in existing_properties:
+        result["gid_min"] = DEFAULT_GID_MIN
+
     if "uid_min" not in existing_properties:
         result["uid_min"] = DEFAULT_UID_MIN