diff mbox series

[RFC] libsepol,checkpolicy: Add netlink xperm support

Message ID 20211110111146.3461631-1-brambonne@google.com (mailing list archive)
State Changes Requested
Headers show
Series [RFC] libsepol,checkpolicy: Add netlink xperm support | expand

Commit Message

Bram Bonné Nov. 10, 2021, 11:11 a.m. UTC
Reuse the existing extended permissions infrastructure to support
sepolicy for different netlink message types.

When individual netlink message types are omitted only the existing
permissions are checked. As is the case for ioctl xperms, this feature
is intended to provide finer granularity for nlmsg_read and nlmsg_write
permissions, as they may be too imprecise. For example, a single
NETLINK_ROUTE socket may provide access to both an interface's IP
address and to its ARP table, which might have different privacy
implications. In addition, the set of message types has grown over time,
so even if the current list is acceptable, future additions might not be.
It was suggested before on the mailing list [1] that extended permissions
would be a good fit for this purpose.

Existing policy using the nlmsg_read and nlmsg_write permissions will
continue to work as-is. Similar to ioctl xperms, netlink xperms allow
for a more fine-grained policy where needed.

Example policy on Android, preventing regular apps from accessing the
device's MAC address and ARP table, but allowing this access to
privileged apps, looks as follows:

allow netdomain self:netlink_route_socket {
        create read getattr write setattr lock append connect getopt
        setopt shutdown nlmsg_read
};
allowxperm netdomain self:netlink_route_socket nlmsg ~{
        RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
};
allowxperm priv_app self:netlink_route_socket nlmsg {
        RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
};

Android currently uses code similar to [1] as a temporary workaround to
limit access to certain netlink message types; our hope is that this patch
will allow us to move back to upstream code with an approach that works for
everyone.

[1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/

Signed-off-by: Bram Bonne <brambonne@google.com>
---
 checkpolicy/policy_define.c                | 124 +++++++++++------
 checkpolicy/test/dismod.c                  |   2 +
 libsepol/cil/src/cil.c                     |   2 +
 libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
 libsepol/cil/src/cil_build_ast.c           |   4 +-
 libsepol/cil/src/cil_internal.h            |   2 +
 libsepol/cil/src/cil_policy.c              |   2 +
 libsepol/cil/src/cil_verify.c              |   3 +
 libsepol/cil/src/cil_write_ast.c           |  16 ++-
 libsepol/include/sepol/policydb/avtab.h    |   1 +
 libsepol/include/sepol/policydb/policydb.h |   1 +
 libsepol/src/assertion.c                   |  15 +-
 libsepol/src/expand.c                      |   3 +
 libsepol/src/kernel_to_cil.c               |  23 ++-
 libsepol/src/module_to_cil.c               |  29 +++-
 libsepol/src/optimize.c                    |   6 +
 libsepol/src/util.c                        |  11 +-
 17 files changed, 321 insertions(+), 77 deletions(-)

Comments

James Carter Nov. 15, 2021, 8:07 p.m. UTC | #1
On Wed, Nov 10, 2021 at 6:12 AM Bram Bonne <brambonne@google.com> wrote:
>
> Reuse the existing extended permissions infrastructure to support
> sepolicy for different netlink message types.
>
> When individual netlink message types are omitted only the existing
> permissions are checked. As is the case for ioctl xperms, this feature
> is intended to provide finer granularity for nlmsg_read and nlmsg_write
> permissions, as they may be too imprecise. For example, a single
> NETLINK_ROUTE socket may provide access to both an interface's IP
> address and to its ARP table, which might have different privacy
> implications. In addition, the set of message types has grown over time,
> so even if the current list is acceptable, future additions might not be.
> It was suggested before on the mailing list [1] that extended permissions
> would be a good fit for this purpose.
>
> Existing policy using the nlmsg_read and nlmsg_write permissions will
> continue to work as-is. Similar to ioctl xperms, netlink xperms allow
> for a more fine-grained policy where needed.
>
> Example policy on Android, preventing regular apps from accessing the
> device's MAC address and ARP table, but allowing this access to
> privileged apps, looks as follows:
>
> allow netdomain self:netlink_route_socket {
>         create read getattr write setattr lock append connect getopt
>         setopt shutdown nlmsg_read
> };
> allowxperm netdomain self:netlink_route_socket nlmsg ~{
>         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL

Where are these defined? What are the valid values for userspace?

Thanks,
Jim


> };
> allowxperm priv_app self:netlink_route_socket nlmsg {
>         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> };
>
> Android currently uses code similar to [1] as a temporary workaround to
> limit access to certain netlink message types; our hope is that this patch
> will allow us to move back to upstream code with an approach that works for
> everyone.
>
> [1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/
>
> Signed-off-by: Bram Bonne <brambonne@google.com>
> ---
>  checkpolicy/policy_define.c                | 124 +++++++++++------
>  checkpolicy/test/dismod.c                  |   2 +
>  libsepol/cil/src/cil.c                     |   2 +
>  libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
>  libsepol/cil/src/cil_build_ast.c           |   4 +-
>  libsepol/cil/src/cil_internal.h            |   2 +
>  libsepol/cil/src/cil_policy.c              |   2 +
>  libsepol/cil/src/cil_verify.c              |   3 +
>  libsepol/cil/src/cil_write_ast.c           |  16 ++-
>  libsepol/include/sepol/policydb/avtab.h    |   1 +
>  libsepol/include/sepol/policydb/policydb.h |   1 +
>  libsepol/src/assertion.c                   |  15 +-
>  libsepol/src/expand.c                      |   3 +
>  libsepol/src/kernel_to_cil.c               |  23 ++-
>  libsepol/src/module_to_cil.c               |  29 +++-
>  libsepol/src/optimize.c                    |   6 +
>  libsepol/src/util.c                        |  11 +-
>  17 files changed, 321 insertions(+), 77 deletions(-)
>
> diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> index d3eb6111..8ca22993 100644
> --- a/checkpolicy/policy_define.c
> +++ b/checkpolicy/policy_define.c
> @@ -1818,27 +1818,27 @@ avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
>         return sl;
>  }
>
> -typedef struct av_ioctl_range {
> +typedef struct av_xperm_range {
>         uint16_t low;
>         uint16_t high;
> -} av_ioctl_range_t;
> +} av_xperm_range_t;
>
> -struct av_ioctl_range_list {
> +struct av_xperm_range_list {
>         uint8_t omit;
> -       av_ioctl_range_t range;
> -       struct av_ioctl_range_list *next;
> +       av_xperm_range_t range;
> +       struct av_xperm_range_list *next;
>  };
>
> -static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
> +       struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
>
>         /* order list by range.low */
>         for (r = *rangehead; r != NULL; r = r->next) {
> -               sorted = malloc(sizeof(struct av_ioctl_range_list));
> +               sorted = malloc(sizeof(struct av_xperm_range_list));
>                 if (sorted == NULL)
>                         goto error;
> -               memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
> +               memcpy(sorted, r, sizeof(struct av_xperm_range_list));
>                 sorted->next = NULL;
>                 if (sortedhead == NULL) {
>                         sortedhead = sorted;
> @@ -1877,9 +1877,9 @@ error:
>         return -1;
>  }
>
> -static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *r, *tmp;
> +       struct av_xperm_range_list *r, *tmp;
>         r = *rangehead;
>         while (r != NULL && r->next != NULL) {
>                 /* merge */
> @@ -1897,15 +1897,15 @@ static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
>         return 0;
>  }
>
> -static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
>  {
>         char *id;
> -       struct av_ioctl_range_list *rnew, *r = NULL;
> +       struct av_xperm_range_list *rnew, *r = NULL;
>         uint8_t omit = 0;
>
>         *rangehead = NULL;
>
> -       /* read in all the ioctl commands */
> +       /* read in all the netlink ranges / ioctl commands */
>         while ((id = queue_remove(id_queue))) {
>                 if (strcmp(id,"~") == 0) {
>                         /* these are values to be omitted */
> @@ -1917,13 +1917,13 @@ static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
>                         id = queue_remove(id_queue);
>                         r->range.high = (uint16_t) strtoul(id,NULL,0);
>                         if (r->range.high < r->range.low) {
> -                               yyerror("Ioctl ranges must be in ascending order.");
> +                               yyerror("xperm ranges must be in ascending order.");
>                                 return -1;
>                         }
>                         free(id);
>                 } else {
>                         /* read in new low value */
> -                       rnew = malloc(sizeof(struct av_ioctl_range_list));
> +                       rnew = malloc(sizeof(struct av_xperm_range_list));
>                         if (rnew == NULL)
>                                 goto error;
>                         rnew->next = NULL;
> @@ -1950,11 +1950,11 @@ error:
>  }
>
>  /* flip to included ranges */
> -static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
> +       struct av_xperm_range_list *rnew, *r, *newhead, *r2;
>
> -       rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> +       rnew = calloc(1, sizeof(struct av_xperm_range_list));
>         if (!rnew)
>                 goto error;
>
> @@ -1972,7 +1972,7 @@ static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
>
>         while (r) {
>                 r2->range.high = r->range.low - 1;
> -               rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> +               rnew = calloc(1, sizeof(struct av_xperm_range_list));
>                 if (!rnew)
>                         goto error;
>                 r2->next = rnew;
> @@ -1998,27 +1998,27 @@ error:
>         return -1;
>  }
>
> -static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
> +static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
>  {
> -       struct av_ioctl_range_list *rangehead;
> +       struct av_xperm_range_list *rangehead;
>         uint8_t omit;
>
>         /* read in ranges to include and omit */
> -       if (avrule_read_ioctls(&rangehead))
> +       if (avrule_read_xperm_ranges(&rangehead))
>                 return -1;
>         if (rangehead == NULL) {
> -               yyerror("error processing ioctl commands");
> +               yyerror("error processing ioctl/netlink commands");
>                 return -1;
>         }
>         omit = rangehead->omit;
> -       /* sort and merge the input ioctls */
> -       if (avrule_sort_ioctls(&rangehead))
> +       /* sort and merge the input ranges */
> +       if (avrule_sort_xperms(&rangehead))
>                 return -1;
> -       if (avrule_merge_ioctls(&rangehead))
> +       if (avrule_merge_xperms(&rangehead))
>                 return -1;
>         /* flip ranges if these are omitted */
>         if (omit) {
> -               if (avrule_omit_ioctls(&rangehead))
> +               if (avrule_omit_xperms(&rangehead))
>                         return -1;
>         }
>
> @@ -2189,11 +2189,11 @@ static int avrule_xperms_used(const av_extended_perms_t *xperms)
>  #define IOC_DRIV(x) ((x) >> 8)
>  #define IOC_FUNC(x) ((x) & 0xff)
>  #define IOC_CMD(driver, func) (((driver) << 8) + (func))
> -static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> +static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
>                                 av_extended_perms_t *complete_driver,
>                                 av_extended_perms_t **extended_perms)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint8_t low, high;
>
> @@ -2228,10 +2228,10 @@ static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
>
>  }
>
> -static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> +static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
>                         av_extended_perms_t **extended_perms)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint16_t low, high;
>         xperms = calloc(1, sizeof(av_extended_perms_t));
> @@ -2270,10 +2270,10 @@ static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
>         return 0;
>  }
>
> -static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> +static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
>                 av_extended_perms_t **extended_perms, unsigned int driver)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint16_t low, high;
>
> @@ -2307,7 +2307,6 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
>                 high = IOC_FUNC(high);
>                 avrule_xperm_setrangebits(low, high, xperms);
>                 xperms->driver = driver;
> -               xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
>                 r = r->next;
>         }
>
> @@ -2320,6 +2319,18 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
>         return 0;
>  }
>
> +void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
> +{
> +       struct av_xperm_range_list *r, *tmp;
> +
> +       r = rangelist;
> +       while (r) {
> +               tmp = r;
> +               r = r->next;
> +               free(tmp);
> +       }
> +}
> +
>  static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
>  {
>         unsigned int i;
> @@ -2384,13 +2395,13 @@ static int avrule_cpy(avrule_t *dest, const avrule_t *src)
>  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
>  {
>         avrule_t *avrule;
> -       struct av_ioctl_range_list *rangelist, *r;
> +       struct av_xperm_range_list *rangelist, *r;
>         av_extended_perms_t *complete_driver, *partial_driver, *xperms;
>         unsigned int i;
>
>
>         /* organize ioctl ranges */
> -       if (avrule_ioctl_ranges(&rangelist))
> +       if (avrule_xperm_ranges(&rangelist))
>                 return -1;
>
>         /* create rule for ioctl driver types that are entirely enabled */
> @@ -2422,10 +2433,11 @@ static int define_te_avtab_ioctl(const avrule_t *avrule_template)
>          */
>         i = 0;
>         while (xperms_for_each_bit(&i, partial_driver)) {
> -               if (avrule_ioctl_func(rangelist, &xperms, i))
> +               if (avrule_xperms_single_driver(rangelist, &xperms, i))
>                         return -1;
>
>                 if (xperms) {
> +                       xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
>                         avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
>                         if (!avrule) {
>                                 yyerror("out of memory");
> @@ -2451,6 +2463,38 @@ done:
>         return 0;
>  }
>
> +int define_te_avtab_netlink(avrule_t *avrule_template)
> +{
> +       avrule_t *avrule;
> +       struct av_xperm_range_list *range_list;
> +       av_extended_perms_t *xperms = NULL;
> +
> +       /* organize message ranges */
> +       if (avrule_xperm_ranges(&range_list))
> +               return -1;
> +
> +       /* Netlink message types comfortably fit into a single driver
> +        * (see RTM_MAX in uapi/linux/rtnetlink.h)
> +        */
> +       avrule_xperms_single_driver(range_list, &xperms, 0);
> +
> +       if (xperms && avrule_xperms_used(xperms)) {
> +               xperms->specified = AVRULE_XPERMS_NLMSG;
> +               avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> +               if (!avrule) {
> +                       yyerror("out of memory");
> +                       return -1;
> +               }
> +               if (avrule_cpy(avrule, avrule_template))
> +                       return -1;
> +               avrule->xperms = xperms;
> +               append_avrule(avrule);
> +       } else {
> +               free(xperms);
> +       }
> +       return 0;
> +}
> +
>  int define_te_avtab_extended_perms(int which)
>  {
>         char *id;
> @@ -2473,8 +2517,10 @@ int define_te_avtab_extended_perms(int which)
>         id = queue_remove(id_queue);
>         if (strcmp(id,"ioctl") == 0) {
>                 rc = define_te_avtab_ioctl(avrule_template);
> +       } else if (strcmp(id, "nlmsg") == 0) {
> +               rc = define_te_avtab_netlink(avrule_template);
>         } else {
> -               yyerror("only ioctl extended permissions are supported");
> +               yyerror("only ioctl / nlmsg extended permissions are supported");
>                 rc = -1;
>         }
>
> diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> index ec2a3e9a..f8652ec5 100644
> --- a/checkpolicy/test/dismod.c
> +++ b/checkpolicy/test/dismod.c
> @@ -296,6 +296,8 @@ static int display_avrule(avrule_t * avrule, policydb_t * policy,
>                         xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
>                 else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
>                         xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
> +               else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
> +                       xperms.specified = AVTAB_XPERMS_NLMSG;
>                 else {
>                         fprintf(fp, "     ERROR: no valid xperms specified\n");
>                         return -1;
> diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> index 4cc7f87f..fbdb005a 100644
> --- a/libsepol/cil/src/cil.c
> +++ b/libsepol/cil/src/cil.c
> @@ -219,6 +219,7 @@ char *CIL_KEY_DONTAUDITX;
>  char *CIL_KEY_NEVERALLOWX;
>  char *CIL_KEY_PERMISSIONX;
>  char *CIL_KEY_IOCTL;
> +char *CIL_KEY_NLMSG;
>  char *CIL_KEY_UNORDERED;
>  char *CIL_KEY_SRC_INFO;
>  char *CIL_KEY_SRC_CIL;
> @@ -388,6 +389,7 @@ static void cil_init_keys(void)
>         CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
>         CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
>         CIL_KEY_IOCTL = cil_strpool_add("ioctl");
> +       CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
>         CIL_KEY_UNORDERED = cil_strpool_add("unordered");
>         CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
>         CIL_KEY_SRC_CIL = cil_strpool_add("cil");
> diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> index d8aa495a..f9938d66 100644
> --- a/libsepol/cil/src/cil_binary.c
> +++ b/libsepol/cil/src/cil_binary.c
> @@ -66,6 +66,7 @@ struct cil_args_binary {
>         int pass;
>         hashtab_t role_trans_table;
>         hashtab_t avrulex_ioctl_table;
> +       hashtab_t avrulex_nlmsg_table;
>         void **type_value_to_cil;
>  };
>
> @@ -1553,7 +1554,7 @@ void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
>  #define IOC_DRIV(x) (x >> 8)
>  #define IOC_FUNC(x) (x & 0xff)
>
> -int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> +int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
>  {
>         ebitmap_node_t *node;
>         unsigned int i;
> @@ -1618,13 +1619,53 @@ int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
>         return SEPOL_OK;
>  }
>
> -int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> +{
> +       ebitmap_node_t *node;
> +       uint16_t i;
> +       uint16_t low;
> +       struct avtab_extended_perms *avtab = NULL;
> +       int start_new_range = 1;
> +
> +       cil_list_init(xperms_list, CIL_NONE);
> +
> +       ebitmap_for_each_positive_bit(xperms, node, i) {
> +               if (start_new_range) {
> +                       low = i;
> +                       start_new_range = 0;
> +               }
> +
> +               // Continue if the current bit isn't the end of the driver range
> +               // or the next bit is set
> +               if (ebitmap_get_bit(xperms, i + 1)) {
> +                       continue;
> +               }
> +
> +               start_new_range = 1;
> +
> +               if (!avtab) {
> +                       avtab = cil_calloc(1, sizeof(*avtab));
> +                       // Netlink message types all fit in driver 0
> +                       avtab->driver = 0;
> +                       avtab->specified = AVTAB_XPERMS_NLMSG;
> +               }
> +
> +               __avrule_xperm_setrangebits(low, i, avtab);
> +       }
> +
> +       if (avtab) {
> +               cil_list_append(*xperms_list, CIL_NONE, avtab);
> +       }
> +
> +       return SEPOL_OK;
> +}
> +
> +int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
>  {
>         int rc = SEPOL_OK;
>         struct policydb *pdb;
>         avtab_key_t *avtab_key;
>         avtab_datum_t avtab_datum;
> -       struct cil_list *xperms_list = NULL;
>         struct cil_list_item *item;
>         class_datum_t *sepol_obj;
>         uint32_t data = 0;
> @@ -1637,17 +1678,12 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
>         // setting the data for an extended avtab isn't really necessary because
>         // it is ignored by the kernel. However, neverallow checking requires that
>         // the data value be set, so set it for that to work.
> -       rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
> +       rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
>         if (rc != SEPOL_OK) {
>                 goto exit;
>         }
>         avtab_datum.data = data;
>
> -       rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> -       if (rc != SEPOL_OK) {
> -               goto exit;
> -       }
> -
>         cil_list_for_each(item, xperms_list) {
>                 avtab_datum.xperms = item->data;
>                 rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
> @@ -1659,16 +1695,57 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
>         rc = SEPOL_OK;
>
>  exit:
> +       return rc;
> +}
> +
> +void __cil_cleanup_xperms(struct cil_list *xperms_list)
> +{
> +       struct cil_list_item *item;
> +
>         if (xperms_list != NULL) {
>                 cil_list_for_each(item, xperms_list) {
>                         free(item->data);
> +                       item->data = NULL;
>                 }
>                 cil_list_destroy(&xperms_list, CIL_FALSE);
>         }
> +}
> +
> +int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +{
> +       struct cil_list *xperms_list = NULL;
> +       int rc;
> +
> +       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> +       if (rc != SEPOL_OK) {
> +               goto exit;
> +       }
> +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
> +
> +exit:
> +       __cil_cleanup_xperms(xperms_list);
> +
>         return rc;
>  }
>
> -int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> +int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +{
> +       struct cil_list *xperms_list = NULL;
> +       int rc;
> +
> +       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> +       if (rc != SEPOL_OK) {
> +               goto exit;
> +       }
> +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
> +
> +exit:
> +       __cil_cleanup_xperms(xperms_list);
> +
> +       return rc;
> +}
> +
> +int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
>  {
>         uint16_t specified;
>         avtab_key_t *avtab_key;
> @@ -1747,8 +1824,12 @@ int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
>                 if (rc != SEPOL_OK) goto exit;
>
>                 switch (permx->kind) {
> -               case  CIL_PERMX_KIND_IOCTL:
> -                       rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> +               case CIL_PERMX_KIND_IOCTL:
> +                       rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> +                       if (rc != SEPOL_OK) goto exit;
> +                       break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
>                         if (rc != SEPOL_OK) goto exit;
>                         break;
>                 default:
> @@ -4417,6 +4498,9 @@ static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
>                         case CIL_PERMX_KIND_IOCTL:
>                                 perm_str = CIL_KEY_IOCTL;
>                                 break;
> +                       case CIL_PERMX_KIND_NLMSG:
> +                               perm_str = CIL_KEY_NLMSG;
> +                               break;
>                         default:
>                                 rc = SEPOL_ERR;
>                                 goto exit;
> @@ -4563,6 +4647,9 @@ static void __cil_print_permissionx(struct cil_permissionx *px)
>                 case CIL_PERMX_KIND_IOCTL:
>                         kind_str = CIL_KEY_IOCTL;
>                         break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       kind_str = CIL_KEY_NLMSG;
> +                       break;
>                 default:
>                         kind_str = "unknown";
>                         break;
> @@ -4696,8 +4783,21 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
>                         goto exit;
>                 }
>
> -               rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> -               if (rc != SEPOL_OK) {
> +               switch (cil_rule->perms.x.permx->kind) {
> +               case CIL_PERMX_KIND_IOCTL:
> +                       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> +                       if (rc != SEPOL_OK) {
> +                               goto exit;
> +                       }
> +                       break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> +                       if (rc != SEPOL_OK) {
> +                               goto exit;
> +                       }
> +                       break;
> +               default:
> +                       rc = SEPOL_ERR;
>                         goto exit;
>                 }
>
> @@ -4715,13 +4815,7 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
>         }
>
>  exit:
> -       if (xperms != NULL) {
> -               cil_list_for_each(item, xperms) {
> -                       free(item->data);
> -                       item->data = NULL;
> -               }
> -               cil_list_destroy(&xperms, CIL_FALSE);
> -       }
> +       __cil_cleanup_xperms(xperms);
>
>         rule->xperms = NULL;
>         __cil_destroy_sepol_avrules(rule);
> @@ -4904,6 +4998,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>         struct cil_list *neverallows = NULL;
>         hashtab_t role_trans_table = NULL;
>         hashtab_t avrulex_ioctl_table = NULL;
> +       hashtab_t avrulex_nlmsg_table = NULL;
>         void **type_value_to_cil = NULL;
>         struct cil_class **class_value_to_cil = NULL;
>         struct cil_perm ***perm_value_to_cil = NULL;
> @@ -4947,7 +5042,13 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>
>         avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
>         if (!avrulex_ioctl_table) {
> -               cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
> +               cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
> +               goto exit;
> +       }
> +
> +       avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> +       if (!avrulex_nlmsg_table) {
> +               cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
>                 goto exit;
>         }
>
> @@ -4958,6 +5059,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>         extra_args.neverallows = neverallows;
>         extra_args.role_trans_table = role_trans_table;
>         extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
> +       extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
>         extra_args.type_value_to_cil = type_value_to_cil;
>
>         for (i = 1; i <= 3; i++) {
> @@ -4980,7 +5082,12 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>                 if (i == 3) {
>                         rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
>                         if (rc != SEPOL_OK) {
> -                               cil_log(CIL_INFO, "Failure creating avrulex rules\n");
> +                               cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
> +                               goto exit;
> +                       }
> +                       rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
> +                       if (rc != SEPOL_OK) {
> +                               cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
>                                 goto exit;
>                         }
>                 }
> @@ -5056,6 +5163,7 @@ exit:
>         hashtab_destroy(role_trans_table);
>         hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
>         hashtab_destroy(avrulex_ioctl_table);
> +       hashtab_destroy(avrulex_nlmsg_table);
>         free(type_value_to_cil);
>         free(class_value_to_cil);
>         if (perm_value_to_cil != NULL) {
> diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> index 9c34be23..b1cfb4f0 100644
> --- a/libsepol/cil/src/cil_build_ast.c
> +++ b/libsepol/cil/src/cil_build_ast.c
> @@ -2159,8 +2159,10 @@ int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
>
>         if (parse_current->data == CIL_KEY_IOCTL) {
>                 permx->kind = CIL_PERMX_KIND_IOCTL;
> +       } else if (parse_current->data == CIL_KEY_NLMSG) {
> +               permx->kind = CIL_PERMX_KIND_NLMSG;
>         } else {
> -               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
> +               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
>                 rc = SEPOL_ERR;
>                 goto exit;
>         }
> diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> index 6f1d3cb5..c968cf1e 100644
> --- a/libsepol/cil/src/cil_internal.h
> +++ b/libsepol/cil/src/cil_internal.h
> @@ -236,6 +236,7 @@ extern char *CIL_KEY_DONTAUDITX;
>  extern char *CIL_KEY_NEVERALLOWX;
>  extern char *CIL_KEY_PERMISSIONX;
>  extern char *CIL_KEY_IOCTL;
> +extern char *CIL_KEY_NLMSG;
>  extern char *CIL_KEY_UNORDERED;
>  extern char *CIL_KEY_SRC_INFO;
>  extern char *CIL_KEY_SRC_CIL;
> @@ -623,6 +624,7 @@ struct cil_avrule {
>  };
>
>  #define CIL_PERMX_KIND_IOCTL 1
> +#define CIL_PERMX_KIND_NLMSG 2
>  struct cil_permissionx {
>         struct cil_symtab_datum datum;
>         uint32_t kind;
> diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> index 7c543c47..2703d764 100644
> --- a/libsepol/cil/src/cil_policy.c
> +++ b/libsepol/cil/src/cil_policy.c
> @@ -1112,6 +1112,8 @@ static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
>
>         if (permx->kind == CIL_PERMX_KIND_IOCTL) {
>                 kind = "ioctl";
> +       } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +               kind = CIL_KEY_NLMSG;
>         } else {
>                 kind = "???";
>         }
> diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
> index d994d717..67bdb0e9 100644
> --- a/libsepol/cil/src/cil_verify.c
> +++ b/libsepol/cil/src/cil_verify.c
> @@ -1427,6 +1427,9 @@ int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
>                 case CIL_PERMX_KIND_IOCTL:
>                         kind_str = CIL_KEY_IOCTL;
>                         break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       kind_str = CIL_KEY_NLMSG;
> +                       break;
>                 default:
>                         cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
>                         rc = SEPOL_ERR;
> diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> index d7f00bcc..b93a4fa1 100644
> --- a/libsepol/cil/src/cil_write_ast.c
> +++ b/libsepol/cil/src/cil_write_ast.c
> @@ -303,7 +303,13 @@ static void write_permx(FILE *out, struct cil_permissionx *permx)
>                 fprintf(out, "%s", datum_to_str(DATUM(permx)));
>         } else {
>                 fprintf(out, "(");
> -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> +                       fprintf(out, "%s ", "ioctl");
> +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +                       fprintf(out, "%s ", "nlmsg");
> +               } else {
> +                       fprintf(out, "%s ", "<?KIND>");
> +               }
>                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
>                 write_expr(out, permx->expr_str);
>                 fprintf(out, ")");
> @@ -812,7 +818,13 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
>         case CIL_PERMISSIONX: {
>                 struct cil_permissionx *permx = node->data;
>                 fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
> -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> +                       fprintf(out, "%s ", "ioctl");
> +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +                       fprintf(out, "%s ", "nlmsg");
> +               } else {
> +                       fprintf(out, "%s ", "<?KIND>");
> +               }
>                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
>                 write_expr(out, permx->expr_str);
>                 fprintf(out, "))\n");
> diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
> index 10ecde9a..aa7481d3 100644
> --- a/libsepol/include/sepol/policydb/avtab.h
> +++ b/libsepol/include/sepol/policydb/avtab.h
> @@ -74,6 +74,7 @@ typedef struct avtab_extended_perms {
>
>  #define AVTAB_XPERMS_IOCTLFUNCTION     0x01
>  #define AVTAB_XPERMS_IOCTLDRIVER       0x02
> +#define AVTAB_XPERMS_NLMSG             0x03
>         /* extension of the avtab_key specified */
>         uint8_t specified;
>         uint8_t driver;
> diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> index 4bf9f05d..55918e1d 100644
> --- a/libsepol/include/sepol/policydb/policydb.h
> +++ b/libsepol/include/sepol/policydb/policydb.h
> @@ -259,6 +259,7 @@ typedef struct class_perm_node {
>  typedef struct av_extended_perms {
>  #define AVRULE_XPERMS_IOCTLFUNCTION    0x01
>  #define AVRULE_XPERMS_IOCTLDRIVER      0x02
> +#define AVRULE_XPERMS_NLMSG            0x03
>         uint8_t specified;
>         uint8_t driver;
>         /* 256 bits of permissions */
> diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
> index dd2749a0..47e45713 100644
> --- a/libsepol/src/assertion.c
> +++ b/libsepol/src/assertion.c
> @@ -101,6 +101,9 @@ static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
>         } else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
>                         && (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
>                 rc = extended_permissions_and(neverallow->perms, allow->perms);
> +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> +               rc = extended_permissions_and(neverallow->perms, allow->perms);
>         }
>
>         return rc;
> @@ -133,6 +136,12 @@ static void extended_permissions_violated(avtab_extended_perms_t *result,
>                 result->specified = AVTAB_XPERMS_IOCTLDRIVER;
>                 for (i = 0; i < EXTENDED_PERMS_LEN; i++)
>                         result->perms[i] = neverallow->perms[i] & allow->perms[i];
> +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> +               result->specified = AVTAB_XPERMS_NLMSG;
> +               result->driver = 0;
> +               for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> +                       result->perms[i] = neverallow->perms[i] & allow->perms[i];
>         }
>  }
>
> @@ -166,7 +175,8 @@ static int report_assertion_extended_permissions(sepol_handle_t *handle,
>                              node = avtab_search_node_next(node, tmp_key.specified)) {
>                                 xperms = node->datum.xperms;
>                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                                         continue;
>
>                                 rc = check_extended_permissions(avrule->xperms, xperms);
> @@ -346,7 +356,8 @@ static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
>                                 xperms = node->datum.xperms;
>
>                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                                         continue;
>                                 rc = check_extended_permissions(neverallow_xperms, xperms);
>                                 if (rc)
> diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> index a6a466f7..959e6eb2 100644
> --- a/libsepol/src/expand.c
> +++ b/libsepol/src/expand.c
> @@ -1796,6 +1796,9 @@ static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
>         case AVRULE_XPERMS_IOCTLDRIVER:
>                 xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
>                 break;
> +       case AVRULE_XPERMS_NLMSG:
> +               xperms->specified = AVTAB_XPERMS_NLMSG;
> +               break;
>         default:
>                 return -1;
>         }
> diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> index 305567a5..090d4e43 100644
> --- a/libsepol/src/kernel_to_cil.c
> +++ b/libsepol/src/kernel_to_cil.c
> @@ -1617,7 +1617,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
>         remaining = sizeof(xpermsbuf);
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG)) {
>                 return NULL;
>         }
>
> @@ -1637,7 +1638,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
>                         continue;
>                 }
>
> -               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
> +               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
> +                       || xperms->specified & AVTAB_XPERMS_NLMSG) {
>                         value = xperms->driver<<8 | bit;
>                         if (in_range) {
>                                 low_value = xperms->driver<<8 | low_bit;
> @@ -1679,7 +1681,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
>  {
>         uint32_t data = datum->data;
>         type_datum_t *type;
> -       const char *flavor, *tgt;
> +       const char *flavor, *tgt, *func;
>         char *src, *class, *perms, *new;
>         char *rule = NULL;
>
> @@ -1742,8 +1744,21 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
>                         goto exit;
>                 }
>
> +               switch (datum->xperms->specified) {
> +               case AVRULE_XPERMS_IOCTLDRIVER:
> +               case AVRULE_XPERMS_IOCTLFUNCTION:
> +                       func = "ioctl";
> +                       break;
> +               case AVRULE_XPERMS_NLMSG:
> +                       func = "nlmsg";
> +                       break;
> +               default:
> +                       sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
> +                       goto exit;
> +               }
> +
>                 rule = create_str("(%s %s %s (%s %s (%s)))", 6,
> -                                 flavor, src, tgt, "ioctl", class, perms);
> +                                 flavor, src, tgt, func, class, perms);
>         } else {
>                 new = pdb->p_type_val_to_name[data - 1];
>
> diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> index 16e4004e..47d9b171 100644
> --- a/libsepol/src/module_to_cil.c
> +++ b/libsepol/src/module_to_cil.c
> @@ -627,14 +627,15 @@ exit:
>  static int xperms_to_cil(const av_extended_perms_t *xperms)
>  {
>         uint16_t value;
> -       uint16_t low_bit;
> +       uint16_t low_bit = 0;
>         uint16_t low_value;
>         unsigned int bit;
>         unsigned int in_range = 0;
>         int first = 1;
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                 return -1;
>
>         for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
> @@ -674,6 +675,13 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
>                         } else {
>                                 cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
>                         }
> +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> +                       if (in_range) {
> +                               cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
> +                               in_range = 0;
> +                       } else {
> +                               cil_printf("0x%hx", (uint16_t) bit);
> +                       }
>                 }
>         }
>
> @@ -683,7 +691,7 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
>  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
>  {
>         int rc = -1;
> -       const char *rule;
> +       const char *rule, *func;
>         const struct class_perm_node *classperm;
>
>         switch (type) {
> @@ -705,10 +713,23 @@ static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
>                 goto exit;
>         }
>
> +       switch (xperms->specified) {
> +       case AVRULE_XPERMS_IOCTLDRIVER:
> +       case AVRULE_XPERMS_IOCTLFUNCTION:
> +               func = "ioctl";
> +               break;
> +       case AVRULE_XPERMS_NLMSG:
> +               func = "nlmsg";
> +               break;
> +       default:
> +               log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
> +               goto exit;
> +       }
> +
>         for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
>                 cil_indent(indent);
>                 cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
> -                          "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
> +                          func, pdb->p_class_val_to_name[classperm->tclass - 1]);
>                 xperms_to_cil(xperms);
>                 cil_printf(")))\n");
>         }
> diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
> index 6826155c..f23cfd17 100644
> --- a/libsepol/src/optimize.c
> +++ b/libsepol/src/optimize.c
> @@ -180,6 +180,12 @@ static int process_avtab_datum(uint16_t specified,
>
>                         if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
>                                 return process_xperms(x1->perms, x2->perms);
> +               } else if (x1->specified == AVTAB_XPERMS_NLMSG) {
> +                       if (x2->specified == AVTAB_XPERMS_NLMSG) {
> +                               if (x1->driver != x2->driver)
> +                                       return 0;
> +                               return process_xperms(x1->perms, x2->perms);
> +                       }
>                 }
>                 return 0;
>         }
> diff --git a/libsepol/src/util.c b/libsepol/src/util.c
> index 902c63c5..b5b41f87 100644
> --- a/libsepol/src/util.c
> +++ b/libsepol/src/util.c
> @@ -124,7 +124,7 @@ char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
>  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>  {
>         uint16_t value;
> -       uint16_t low_bit;
> +       uint16_t low_bit = 0;
>         uint16_t low_value;
>         unsigned int bit;
>         unsigned int in_range = 0;
> @@ -135,7 +135,8 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>         p = xpermsbuf;
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                 return NULL;
>
>         len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");
> @@ -173,6 +174,12 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>                                 len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
>                         }
>
> +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> +                       if (in_range) {
> +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
> +                       } else {
> +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
> +                       }
>                 }
>
>                 if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))
> --
> 2.34.0.rc0.344.g81b53c2807-goog
>
Bram Bonné Nov. 16, 2021, 9:11 a.m. UTC | #2
On Mon, Nov 15, 2021 at 9:08 PM James Carter <jwcart2@gmail.com> wrote:
>
> On Wed, Nov 10, 2021 at 6:12 AM Bram Bonne <brambonne@google.com> wrote:
> >
> > Reuse the existing extended permissions infrastructure to support
> > sepolicy for different netlink message types.
> >
> > When individual netlink message types are omitted only the existing
> > permissions are checked. As is the case for ioctl xperms, this feature
> > is intended to provide finer granularity for nlmsg_read and nlmsg_write
> > permissions, as they may be too imprecise. For example, a single
> > NETLINK_ROUTE socket may provide access to both an interface's IP
> > address and to its ARP table, which might have different privacy
> > implications. In addition, the set of message types has grown over time,
> > so even if the current list is acceptable, future additions might not be.
> > It was suggested before on the mailing list [1] that extended permissions
> > would be a good fit for this purpose.
> >
> > Existing policy using the nlmsg_read and nlmsg_write permissions will
> > continue to work as-is. Similar to ioctl xperms, netlink xperms allow
> > for a more fine-grained policy where needed.
> >
> > Example policy on Android, preventing regular apps from accessing the
> > device's MAC address and ARP table, but allowing this access to
> > privileged apps, looks as follows:
> >
> > allow netdomain self:netlink_route_socket {
> >         create read getattr write setattr lock append connect getopt
> >         setopt shutdown nlmsg_read
> > };
> > allowxperm netdomain self:netlink_route_socket nlmsg ~{
> >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
>
> Where are these defined? What are the valid values for userspace?

These values need to be defined in sepolicy, similar to what is the
case for ioctls. [2] has an example of such defines for Android.

Kind regards,
Bram

[2] https://android-review.googlesource.com/c/platform/system/sepolicy/+/1786189/4/public/nlmsg_defines

>
> Thanks,
> Jim
>
>
> > };
> > allowxperm priv_app self:netlink_route_socket nlmsg {
> >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> > };
> >
> > Android currently uses code similar to [1] as a temporary workaround to
> > limit access to certain netlink message types; our hope is that this patch
> > will allow us to move back to upstream code with an approach that works for
> > everyone.
> >
> > [1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/
> >
> > Signed-off-by: Bram Bonne <brambonne@google.com>
> > ---
> >  checkpolicy/policy_define.c                | 124 +++++++++++------
> >  checkpolicy/test/dismod.c                  |   2 +
> >  libsepol/cil/src/cil.c                     |   2 +
> >  libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
> >  libsepol/cil/src/cil_build_ast.c           |   4 +-
> >  libsepol/cil/src/cil_internal.h            |   2 +
> >  libsepol/cil/src/cil_policy.c              |   2 +
> >  libsepol/cil/src/cil_verify.c              |   3 +
> >  libsepol/cil/src/cil_write_ast.c           |  16 ++-
> >  libsepol/include/sepol/policydb/avtab.h    |   1 +
> >  libsepol/include/sepol/policydb/policydb.h |   1 +
> >  libsepol/src/assertion.c                   |  15 +-
> >  libsepol/src/expand.c                      |   3 +
> >  libsepol/src/kernel_to_cil.c               |  23 ++-
> >  libsepol/src/module_to_cil.c               |  29 +++-
> >  libsepol/src/optimize.c                    |   6 +
> >  libsepol/src/util.c                        |  11 +-
> >  17 files changed, 321 insertions(+), 77 deletions(-)
> >
> > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > index d3eb6111..8ca22993 100644
> > --- a/checkpolicy/policy_define.c
> > +++ b/checkpolicy/policy_define.c
> > @@ -1818,27 +1818,27 @@ avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
> >         return sl;
> >  }
> >
> > -typedef struct av_ioctl_range {
> > +typedef struct av_xperm_range {
> >         uint16_t low;
> >         uint16_t high;
> > -} av_ioctl_range_t;
> > +} av_xperm_range_t;
> >
> > -struct av_ioctl_range_list {
> > +struct av_xperm_range_list {
> >         uint8_t omit;
> > -       av_ioctl_range_t range;
> > -       struct av_ioctl_range_list *next;
> > +       av_xperm_range_t range;
> > +       struct av_xperm_range_list *next;
> >  };
> >
> > -static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
> > +       struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
> >
> >         /* order list by range.low */
> >         for (r = *rangehead; r != NULL; r = r->next) {
> > -               sorted = malloc(sizeof(struct av_ioctl_range_list));
> > +               sorted = malloc(sizeof(struct av_xperm_range_list));
> >                 if (sorted == NULL)
> >                         goto error;
> > -               memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
> > +               memcpy(sorted, r, sizeof(struct av_xperm_range_list));
> >                 sorted->next = NULL;
> >                 if (sortedhead == NULL) {
> >                         sortedhead = sorted;
> > @@ -1877,9 +1877,9 @@ error:
> >         return -1;
> >  }
> >
> > -static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *r, *tmp;
> > +       struct av_xperm_range_list *r, *tmp;
> >         r = *rangehead;
> >         while (r != NULL && r->next != NULL) {
> >                 /* merge */
> > @@ -1897,15 +1897,15 @@ static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> >         return 0;
> >  }
> >
> > -static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
> >  {
> >         char *id;
> > -       struct av_ioctl_range_list *rnew, *r = NULL;
> > +       struct av_xperm_range_list *rnew, *r = NULL;
> >         uint8_t omit = 0;
> >
> >         *rangehead = NULL;
> >
> > -       /* read in all the ioctl commands */
> > +       /* read in all the netlink ranges / ioctl commands */
> >         while ((id = queue_remove(id_queue))) {
> >                 if (strcmp(id,"~") == 0) {
> >                         /* these are values to be omitted */
> > @@ -1917,13 +1917,13 @@ static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> >                         id = queue_remove(id_queue);
> >                         r->range.high = (uint16_t) strtoul(id,NULL,0);
> >                         if (r->range.high < r->range.low) {
> > -                               yyerror("Ioctl ranges must be in ascending order.");
> > +                               yyerror("xperm ranges must be in ascending order.");
> >                                 return -1;
> >                         }
> >                         free(id);
> >                 } else {
> >                         /* read in new low value */
> > -                       rnew = malloc(sizeof(struct av_ioctl_range_list));
> > +                       rnew = malloc(sizeof(struct av_xperm_range_list));
> >                         if (rnew == NULL)
> >                                 goto error;
> >                         rnew->next = NULL;
> > @@ -1950,11 +1950,11 @@ error:
> >  }
> >
> >  /* flip to included ranges */
> > -static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
> > +       struct av_xperm_range_list *rnew, *r, *newhead, *r2;
> >
> > -       rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > +       rnew = calloc(1, sizeof(struct av_xperm_range_list));
> >         if (!rnew)
> >                 goto error;
> >
> > @@ -1972,7 +1972,7 @@ static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> >
> >         while (r) {
> >                 r2->range.high = r->range.low - 1;
> > -               rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > +               rnew = calloc(1, sizeof(struct av_xperm_range_list));
> >                 if (!rnew)
> >                         goto error;
> >                 r2->next = rnew;
> > @@ -1998,27 +1998,27 @@ error:
> >         return -1;
> >  }
> >
> > -static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
> > +static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
> >  {
> > -       struct av_ioctl_range_list *rangehead;
> > +       struct av_xperm_range_list *rangehead;
> >         uint8_t omit;
> >
> >         /* read in ranges to include and omit */
> > -       if (avrule_read_ioctls(&rangehead))
> > +       if (avrule_read_xperm_ranges(&rangehead))
> >                 return -1;
> >         if (rangehead == NULL) {
> > -               yyerror("error processing ioctl commands");
> > +               yyerror("error processing ioctl/netlink commands");
> >                 return -1;
> >         }
> >         omit = rangehead->omit;
> > -       /* sort and merge the input ioctls */
> > -       if (avrule_sort_ioctls(&rangehead))
> > +       /* sort and merge the input ranges */
> > +       if (avrule_sort_xperms(&rangehead))
> >                 return -1;
> > -       if (avrule_merge_ioctls(&rangehead))
> > +       if (avrule_merge_xperms(&rangehead))
> >                 return -1;
> >         /* flip ranges if these are omitted */
> >         if (omit) {
> > -               if (avrule_omit_ioctls(&rangehead))
> > +               if (avrule_omit_xperms(&rangehead))
> >                         return -1;
> >         }
> >
> > @@ -2189,11 +2189,11 @@ static int avrule_xperms_used(const av_extended_perms_t *xperms)
> >  #define IOC_DRIV(x) ((x) >> 8)
> >  #define IOC_FUNC(x) ((x) & 0xff)
> >  #define IOC_CMD(driver, func) (((driver) << 8) + (func))
> > -static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> > +static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
> >                                 av_extended_perms_t *complete_driver,
> >                                 av_extended_perms_t **extended_perms)
> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint8_t low, high;
> >
> > @@ -2228,10 +2228,10 @@ static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> >
> >  }
> >
> > -static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> > +static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
> >                         av_extended_perms_t **extended_perms)
> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint16_t low, high;
> >         xperms = calloc(1, sizeof(av_extended_perms_t));
> > @@ -2270,10 +2270,10 @@ static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> >         return 0;
> >  }
> >
> > -static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> > +static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
> >                 av_extended_perms_t **extended_perms, unsigned int driver)
> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint16_t low, high;
> >
> > @@ -2307,7 +2307,6 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> >                 high = IOC_FUNC(high);
> >                 avrule_xperm_setrangebits(low, high, xperms);
> >                 xperms->driver = driver;
> > -               xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> >                 r = r->next;
> >         }
> >
> > @@ -2320,6 +2319,18 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> >         return 0;
> >  }
> >
> > +void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
> > +{
> > +       struct av_xperm_range_list *r, *tmp;
> > +
> > +       r = rangelist;
> > +       while (r) {
> > +               tmp = r;
> > +               r = r->next;
> > +               free(tmp);
> > +       }
> > +}
> > +
> >  static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
> >  {
> >         unsigned int i;
> > @@ -2384,13 +2395,13 @@ static int avrule_cpy(avrule_t *dest, const avrule_t *src)
> >  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> >  {
> >         avrule_t *avrule;
> > -       struct av_ioctl_range_list *rangelist, *r;
> > +       struct av_xperm_range_list *rangelist, *r;
> >         av_extended_perms_t *complete_driver, *partial_driver, *xperms;
> >         unsigned int i;
> >
> >
> >         /* organize ioctl ranges */
> > -       if (avrule_ioctl_ranges(&rangelist))
> > +       if (avrule_xperm_ranges(&rangelist))
> >                 return -1;
> >
> >         /* create rule for ioctl driver types that are entirely enabled */
> > @@ -2422,10 +2433,11 @@ static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> >          */
> >         i = 0;
> >         while (xperms_for_each_bit(&i, partial_driver)) {
> > -               if (avrule_ioctl_func(rangelist, &xperms, i))
> > +               if (avrule_xperms_single_driver(rangelist, &xperms, i))
> >                         return -1;
> >
> >                 if (xperms) {
> > +                       xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> >                         avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> >                         if (!avrule) {
> >                                 yyerror("out of memory");
> > @@ -2451,6 +2463,38 @@ done:
> >         return 0;
> >  }
> >
> > +int define_te_avtab_netlink(avrule_t *avrule_template)
> > +{
> > +       avrule_t *avrule;
> > +       struct av_xperm_range_list *range_list;
> > +       av_extended_perms_t *xperms = NULL;
> > +
> > +       /* organize message ranges */
> > +       if (avrule_xperm_ranges(&range_list))
> > +               return -1;
> > +
> > +       /* Netlink message types comfortably fit into a single driver
> > +        * (see RTM_MAX in uapi/linux/rtnetlink.h)
> > +        */
> > +       avrule_xperms_single_driver(range_list, &xperms, 0);
> > +
> > +       if (xperms && avrule_xperms_used(xperms)) {
> > +               xperms->specified = AVRULE_XPERMS_NLMSG;
> > +               avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> > +               if (!avrule) {
> > +                       yyerror("out of memory");
> > +                       return -1;
> > +               }
> > +               if (avrule_cpy(avrule, avrule_template))
> > +                       return -1;
> > +               avrule->xperms = xperms;
> > +               append_avrule(avrule);
> > +       } else {
> > +               free(xperms);
> > +       }
> > +       return 0;
> > +}
> > +
> >  int define_te_avtab_extended_perms(int which)
> >  {
> >         char *id;
> > @@ -2473,8 +2517,10 @@ int define_te_avtab_extended_perms(int which)
> >         id = queue_remove(id_queue);
> >         if (strcmp(id,"ioctl") == 0) {
> >                 rc = define_te_avtab_ioctl(avrule_template);
> > +       } else if (strcmp(id, "nlmsg") == 0) {
> > +               rc = define_te_avtab_netlink(avrule_template);
> >         } else {
> > -               yyerror("only ioctl extended permissions are supported");
> > +               yyerror("only ioctl / nlmsg extended permissions are supported");
> >                 rc = -1;
> >         }
> >
> > diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> > index ec2a3e9a..f8652ec5 100644
> > --- a/checkpolicy/test/dismod.c
> > +++ b/checkpolicy/test/dismod.c
> > @@ -296,6 +296,8 @@ static int display_avrule(avrule_t * avrule, policydb_t * policy,
> >                         xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
> >                 else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
> >                         xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
> > +               else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
> > +                       xperms.specified = AVTAB_XPERMS_NLMSG;
> >                 else {
> >                         fprintf(fp, "     ERROR: no valid xperms specified\n");
> >                         return -1;
> > diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> > index 4cc7f87f..fbdb005a 100644
> > --- a/libsepol/cil/src/cil.c
> > +++ b/libsepol/cil/src/cil.c
> > @@ -219,6 +219,7 @@ char *CIL_KEY_DONTAUDITX;
> >  char *CIL_KEY_NEVERALLOWX;
> >  char *CIL_KEY_PERMISSIONX;
> >  char *CIL_KEY_IOCTL;
> > +char *CIL_KEY_NLMSG;
> >  char *CIL_KEY_UNORDERED;
> >  char *CIL_KEY_SRC_INFO;
> >  char *CIL_KEY_SRC_CIL;
> > @@ -388,6 +389,7 @@ static void cil_init_keys(void)
> >         CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
> >         CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
> >         CIL_KEY_IOCTL = cil_strpool_add("ioctl");
> > +       CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
> >         CIL_KEY_UNORDERED = cil_strpool_add("unordered");
> >         CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
> >         CIL_KEY_SRC_CIL = cil_strpool_add("cil");
> > diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> > index d8aa495a..f9938d66 100644
> > --- a/libsepol/cil/src/cil_binary.c
> > +++ b/libsepol/cil/src/cil_binary.c
> > @@ -66,6 +66,7 @@ struct cil_args_binary {
> >         int pass;
> >         hashtab_t role_trans_table;
> >         hashtab_t avrulex_ioctl_table;
> > +       hashtab_t avrulex_nlmsg_table;
> >         void **type_value_to_cil;
> >  };
> >
> > @@ -1553,7 +1554,7 @@ void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
> >  #define IOC_DRIV(x) (x >> 8)
> >  #define IOC_FUNC(x) (x & 0xff)
> >
> > -int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > +int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> >  {
> >         ebitmap_node_t *node;
> >         unsigned int i;
> > @@ -1618,13 +1619,53 @@ int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
> >         return SEPOL_OK;
> >  }
> >
> > -int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > +{
> > +       ebitmap_node_t *node;
> > +       uint16_t i;
> > +       uint16_t low;
> > +       struct avtab_extended_perms *avtab = NULL;
> > +       int start_new_range = 1;
> > +
> > +       cil_list_init(xperms_list, CIL_NONE);
> > +
> > +       ebitmap_for_each_positive_bit(xperms, node, i) {
> > +               if (start_new_range) {
> > +                       low = i;
> > +                       start_new_range = 0;
> > +               }
> > +
> > +               // Continue if the current bit isn't the end of the driver range
> > +               // or the next bit is set
> > +               if (ebitmap_get_bit(xperms, i + 1)) {
> > +                       continue;
> > +               }
> > +
> > +               start_new_range = 1;
> > +
> > +               if (!avtab) {
> > +                       avtab = cil_calloc(1, sizeof(*avtab));
> > +                       // Netlink message types all fit in driver 0
> > +                       avtab->driver = 0;
> > +                       avtab->specified = AVTAB_XPERMS_NLMSG;
> > +               }
> > +
> > +               __avrule_xperm_setrangebits(low, i, avtab);
> > +       }
> > +
> > +       if (avtab) {
> > +               cil_list_append(*xperms_list, CIL_NONE, avtab);
> > +       }
> > +
> > +       return SEPOL_OK;
> > +}
> > +
> > +int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
> >  {
> >         int rc = SEPOL_OK;
> >         struct policydb *pdb;
> >         avtab_key_t *avtab_key;
> >         avtab_datum_t avtab_datum;
> > -       struct cil_list *xperms_list = NULL;
> >         struct cil_list_item *item;
> >         class_datum_t *sepol_obj;
> >         uint32_t data = 0;
> > @@ -1637,17 +1678,12 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> >         // setting the data for an extended avtab isn't really necessary because
> >         // it is ignored by the kernel. However, neverallow checking requires that
> >         // the data value be set, so set it for that to work.
> > -       rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
> > +       rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
> >         if (rc != SEPOL_OK) {
> >                 goto exit;
> >         }
> >         avtab_datum.data = data;
> >
> > -       rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > -       if (rc != SEPOL_OK) {
> > -               goto exit;
> > -       }
> > -
> >         cil_list_for_each(item, xperms_list) {
> >                 avtab_datum.xperms = item->data;
> >                 rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
> > @@ -1659,16 +1695,57 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> >         rc = SEPOL_OK;
> >
> >  exit:
> > +       return rc;
> > +}
> > +
> > +void __cil_cleanup_xperms(struct cil_list *xperms_list)
> > +{
> > +       struct cil_list_item *item;
> > +
> >         if (xperms_list != NULL) {
> >                 cil_list_for_each(item, xperms_list) {
> >                         free(item->data);
> > +                       item->data = NULL;
> >                 }
> >                 cil_list_destroy(&xperms_list, CIL_FALSE);
> >         }
> > +}
> > +
> > +int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +{
> > +       struct cil_list *xperms_list = NULL;
> > +       int rc;
> > +
> > +       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > +       if (rc != SEPOL_OK) {
> > +               goto exit;
> > +       }
> > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
> > +
> > +exit:
> > +       __cil_cleanup_xperms(xperms_list);
> > +
> >         return rc;
> >  }
> >
> > -int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> > +int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +{
> > +       struct cil_list *xperms_list = NULL;
> > +       int rc;
> > +
> > +       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > +       if (rc != SEPOL_OK) {
> > +               goto exit;
> > +       }
> > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
> > +
> > +exit:
> > +       __cil_cleanup_xperms(xperms_list);
> > +
> > +       return rc;
> > +}
> > +
> > +int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> >  {
> >         uint16_t specified;
> >         avtab_key_t *avtab_key;
> > @@ -1747,8 +1824,12 @@ int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
> >                 if (rc != SEPOL_OK) goto exit;
> >
> >                 switch (permx->kind) {
> > -               case  CIL_PERMX_KIND_IOCTL:
> > -                       rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > +               case CIL_PERMX_KIND_IOCTL:
> > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > +                       if (rc != SEPOL_OK) goto exit;
> > +                       break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> >                         if (rc != SEPOL_OK) goto exit;
> >                         break;
> >                 default:
> > @@ -4417,6 +4498,9 @@ static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
> >                         case CIL_PERMX_KIND_IOCTL:
> >                                 perm_str = CIL_KEY_IOCTL;
> >                                 break;
> > +                       case CIL_PERMX_KIND_NLMSG:
> > +                               perm_str = CIL_KEY_NLMSG;
> > +                               break;
> >                         default:
> >                                 rc = SEPOL_ERR;
> >                                 goto exit;
> > @@ -4563,6 +4647,9 @@ static void __cil_print_permissionx(struct cil_permissionx *px)
> >                 case CIL_PERMX_KIND_IOCTL:
> >                         kind_str = CIL_KEY_IOCTL;
> >                         break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       kind_str = CIL_KEY_NLMSG;
> > +                       break;
> >                 default:
> >                         kind_str = "unknown";
> >                         break;
> > @@ -4696,8 +4783,21 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> >                         goto exit;
> >                 }
> >
> > -               rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > -               if (rc != SEPOL_OK) {
> > +               switch (cil_rule->perms.x.permx->kind) {
> > +               case CIL_PERMX_KIND_IOCTL:
> > +                       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > +                       if (rc != SEPOL_OK) {
> > +                               goto exit;
> > +                       }
> > +                       break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > +                       if (rc != SEPOL_OK) {
> > +                               goto exit;
> > +                       }
> > +                       break;
> > +               default:
> > +                       rc = SEPOL_ERR;
> >                         goto exit;
> >                 }
> >
> > @@ -4715,13 +4815,7 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> >         }
> >
> >  exit:
> > -       if (xperms != NULL) {
> > -               cil_list_for_each(item, xperms) {
> > -                       free(item->data);
> > -                       item->data = NULL;
> > -               }
> > -               cil_list_destroy(&xperms, CIL_FALSE);
> > -       }
> > +       __cil_cleanup_xperms(xperms);
> >
> >         rule->xperms = NULL;
> >         __cil_destroy_sepol_avrules(rule);
> > @@ -4904,6 +4998,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >         struct cil_list *neverallows = NULL;
> >         hashtab_t role_trans_table = NULL;
> >         hashtab_t avrulex_ioctl_table = NULL;
> > +       hashtab_t avrulex_nlmsg_table = NULL;
> >         void **type_value_to_cil = NULL;
> >         struct cil_class **class_value_to_cil = NULL;
> >         struct cil_perm ***perm_value_to_cil = NULL;
> > @@ -4947,7 +5042,13 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >
> >         avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> >         if (!avrulex_ioctl_table) {
> > -               cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
> > +               cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
> > +               goto exit;
> > +       }
> > +
> > +       avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> > +       if (!avrulex_nlmsg_table) {
> > +               cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
> >                 goto exit;
> >         }
> >
> > @@ -4958,6 +5059,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >         extra_args.neverallows = neverallows;
> >         extra_args.role_trans_table = role_trans_table;
> >         extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
> > +       extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
> >         extra_args.type_value_to_cil = type_value_to_cil;
> >
> >         for (i = 1; i <= 3; i++) {
> > @@ -4980,7 +5082,12 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >                 if (i == 3) {
> >                         rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
> >                         if (rc != SEPOL_OK) {
> > -                               cil_log(CIL_INFO, "Failure creating avrulex rules\n");
> > +                               cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
> > +                               goto exit;
> > +                       }
> > +                       rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
> > +                       if (rc != SEPOL_OK) {
> > +                               cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
> >                                 goto exit;
> >                         }
> >                 }
> > @@ -5056,6 +5163,7 @@ exit:
> >         hashtab_destroy(role_trans_table);
> >         hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
> >         hashtab_destroy(avrulex_ioctl_table);
> > +       hashtab_destroy(avrulex_nlmsg_table);
> >         free(type_value_to_cil);
> >         free(class_value_to_cil);
> >         if (perm_value_to_cil != NULL) {
> > diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> > index 9c34be23..b1cfb4f0 100644
> > --- a/libsepol/cil/src/cil_build_ast.c
> > +++ b/libsepol/cil/src/cil_build_ast.c
> > @@ -2159,8 +2159,10 @@ int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
> >
> >         if (parse_current->data == CIL_KEY_IOCTL) {
> >                 permx->kind = CIL_PERMX_KIND_IOCTL;
> > +       } else if (parse_current->data == CIL_KEY_NLMSG) {
> > +               permx->kind = CIL_PERMX_KIND_NLMSG;
> >         } else {
> > -               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
> > +               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
> >                 rc = SEPOL_ERR;
> >                 goto exit;
> >         }
> > diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> > index 6f1d3cb5..c968cf1e 100644
> > --- a/libsepol/cil/src/cil_internal.h
> > +++ b/libsepol/cil/src/cil_internal.h
> > @@ -236,6 +236,7 @@ extern char *CIL_KEY_DONTAUDITX;
> >  extern char *CIL_KEY_NEVERALLOWX;
> >  extern char *CIL_KEY_PERMISSIONX;
> >  extern char *CIL_KEY_IOCTL;
> > +extern char *CIL_KEY_NLMSG;
> >  extern char *CIL_KEY_UNORDERED;
> >  extern char *CIL_KEY_SRC_INFO;
> >  extern char *CIL_KEY_SRC_CIL;
> > @@ -623,6 +624,7 @@ struct cil_avrule {
> >  };
> >
> >  #define CIL_PERMX_KIND_IOCTL 1
> > +#define CIL_PERMX_KIND_NLMSG 2
> >  struct cil_permissionx {
> >         struct cil_symtab_datum datum;
> >         uint32_t kind;
> > diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> > index 7c543c47..2703d764 100644
> > --- a/libsepol/cil/src/cil_policy.c
> > +++ b/libsepol/cil/src/cil_policy.c
> > @@ -1112,6 +1112,8 @@ static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
> >
> >         if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> >                 kind = "ioctl";
> > +       } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +               kind = CIL_KEY_NLMSG;
> >         } else {
> >                 kind = "???";
> >         }
> > diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
> > index d994d717..67bdb0e9 100644
> > --- a/libsepol/cil/src/cil_verify.c
> > +++ b/libsepol/cil/src/cil_verify.c
> > @@ -1427,6 +1427,9 @@ int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
> >                 case CIL_PERMX_KIND_IOCTL:
> >                         kind_str = CIL_KEY_IOCTL;
> >                         break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       kind_str = CIL_KEY_NLMSG;
> > +                       break;
> >                 default:
> >                         cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
> >                         rc = SEPOL_ERR;
> > diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> > index d7f00bcc..b93a4fa1 100644
> > --- a/libsepol/cil/src/cil_write_ast.c
> > +++ b/libsepol/cil/src/cil_write_ast.c
> > @@ -303,7 +303,13 @@ static void write_permx(FILE *out, struct cil_permissionx *permx)
> >                 fprintf(out, "%s", datum_to_str(DATUM(permx)));
> >         } else {
> >                 fprintf(out, "(");
> > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > +                       fprintf(out, "%s ", "ioctl");
> > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +                       fprintf(out, "%s ", "nlmsg");
> > +               } else {
> > +                       fprintf(out, "%s ", "<?KIND>");
> > +               }
> >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> >                 write_expr(out, permx->expr_str);
> >                 fprintf(out, ")");
> > @@ -812,7 +818,13 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
> >         case CIL_PERMISSIONX: {
> >                 struct cil_permissionx *permx = node->data;
> >                 fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
> > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > +                       fprintf(out, "%s ", "ioctl");
> > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +                       fprintf(out, "%s ", "nlmsg");
> > +               } else {
> > +                       fprintf(out, "%s ", "<?KIND>");
> > +               }
> >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> >                 write_expr(out, permx->expr_str);
> >                 fprintf(out, "))\n");
> > diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
> > index 10ecde9a..aa7481d3 100644
> > --- a/libsepol/include/sepol/policydb/avtab.h
> > +++ b/libsepol/include/sepol/policydb/avtab.h
> > @@ -74,6 +74,7 @@ typedef struct avtab_extended_perms {
> >
> >  #define AVTAB_XPERMS_IOCTLFUNCTION     0x01
> >  #define AVTAB_XPERMS_IOCTLDRIVER       0x02
> > +#define AVTAB_XPERMS_NLMSG             0x03
> >         /* extension of the avtab_key specified */
> >         uint8_t specified;
> >         uint8_t driver;
> > diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> > index 4bf9f05d..55918e1d 100644
> > --- a/libsepol/include/sepol/policydb/policydb.h
> > +++ b/libsepol/include/sepol/policydb/policydb.h
> > @@ -259,6 +259,7 @@ typedef struct class_perm_node {
> >  typedef struct av_extended_perms {
> >  #define AVRULE_XPERMS_IOCTLFUNCTION    0x01
> >  #define AVRULE_XPERMS_IOCTLDRIVER      0x02
> > +#define AVRULE_XPERMS_NLMSG            0x03
> >         uint8_t specified;
> >         uint8_t driver;
> >         /* 256 bits of permissions */
> > diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
> > index dd2749a0..47e45713 100644
> > --- a/libsepol/src/assertion.c
> > +++ b/libsepol/src/assertion.c
> > @@ -101,6 +101,9 @@ static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
> >         } else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
> >                         && (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
> >                 rc = extended_permissions_and(neverallow->perms, allow->perms);
> > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > +               rc = extended_permissions_and(neverallow->perms, allow->perms);
> >         }
> >
> >         return rc;
> > @@ -133,6 +136,12 @@ static void extended_permissions_violated(avtab_extended_perms_t *result,
> >                 result->specified = AVTAB_XPERMS_IOCTLDRIVER;
> >                 for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> >                         result->perms[i] = neverallow->perms[i] & allow->perms[i];
> > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > +               result->specified = AVTAB_XPERMS_NLMSG;
> > +               result->driver = 0;
> > +               for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> > +                       result->perms[i] = neverallow->perms[i] & allow->perms[i];
> >         }
> >  }
> >
> > @@ -166,7 +175,8 @@ static int report_assertion_extended_permissions(sepol_handle_t *handle,
> >                              node = avtab_search_node_next(node, tmp_key.specified)) {
> >                                 xperms = node->datum.xperms;
> >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                                         continue;
> >
> >                                 rc = check_extended_permissions(avrule->xperms, xperms);
> > @@ -346,7 +356,8 @@ static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
> >                                 xperms = node->datum.xperms;
> >
> >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                                         continue;
> >                                 rc = check_extended_permissions(neverallow_xperms, xperms);
> >                                 if (rc)
> > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > index a6a466f7..959e6eb2 100644
> > --- a/libsepol/src/expand.c
> > +++ b/libsepol/src/expand.c
> > @@ -1796,6 +1796,9 @@ static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
> >         case AVRULE_XPERMS_IOCTLDRIVER:
> >                 xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
> >                 break;
> > +       case AVRULE_XPERMS_NLMSG:
> > +               xperms->specified = AVTAB_XPERMS_NLMSG;
> > +               break;
> >         default:
> >                 return -1;
> >         }
> > diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> > index 305567a5..090d4e43 100644
> > --- a/libsepol/src/kernel_to_cil.c
> > +++ b/libsepol/src/kernel_to_cil.c
> > @@ -1617,7 +1617,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> >         remaining = sizeof(xpermsbuf);
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG)) {
> >                 return NULL;
> >         }
> >
> > @@ -1637,7 +1638,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> >                         continue;
> >                 }
> >
> > -               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
> > +               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
> > +                       || xperms->specified & AVTAB_XPERMS_NLMSG) {
> >                         value = xperms->driver<<8 | bit;
> >                         if (in_range) {
> >                                 low_value = xperms->driver<<8 | low_bit;
> > @@ -1679,7 +1681,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> >  {
> >         uint32_t data = datum->data;
> >         type_datum_t *type;
> > -       const char *flavor, *tgt;
> > +       const char *flavor, *tgt, *func;
> >         char *src, *class, *perms, *new;
> >         char *rule = NULL;
> >
> > @@ -1742,8 +1744,21 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> >                         goto exit;
> >                 }
> >
> > +               switch (datum->xperms->specified) {
> > +               case AVRULE_XPERMS_IOCTLDRIVER:
> > +               case AVRULE_XPERMS_IOCTLFUNCTION:
> > +                       func = "ioctl";
> > +                       break;
> > +               case AVRULE_XPERMS_NLMSG:
> > +                       func = "nlmsg";
> > +                       break;
> > +               default:
> > +                       sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
> > +                       goto exit;
> > +               }
> > +
> >                 rule = create_str("(%s %s %s (%s %s (%s)))", 6,
> > -                                 flavor, src, tgt, "ioctl", class, perms);
> > +                                 flavor, src, tgt, func, class, perms);
> >         } else {
> >                 new = pdb->p_type_val_to_name[data - 1];
> >
> > diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> > index 16e4004e..47d9b171 100644
> > --- a/libsepol/src/module_to_cil.c
> > +++ b/libsepol/src/module_to_cil.c
> > @@ -627,14 +627,15 @@ exit:
> >  static int xperms_to_cil(const av_extended_perms_t *xperms)
> >  {
> >         uint16_t value;
> > -       uint16_t low_bit;
> > +       uint16_t low_bit = 0;
> >         uint16_t low_value;
> >         unsigned int bit;
> >         unsigned int in_range = 0;
> >         int first = 1;
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                 return -1;
> >
> >         for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
> > @@ -674,6 +675,13 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> >                         } else {
> >                                 cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
> >                         }
> > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > +                       if (in_range) {
> > +                               cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
> > +                               in_range = 0;
> > +                       } else {
> > +                               cil_printf("0x%hx", (uint16_t) bit);
> > +                       }
> >                 }
> >         }
> >
> > @@ -683,7 +691,7 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> >  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
> >  {
> >         int rc = -1;
> > -       const char *rule;
> > +       const char *rule, *func;
> >         const struct class_perm_node *classperm;
> >
> >         switch (type) {
> > @@ -705,10 +713,23 @@ static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
> >                 goto exit;
> >         }
> >
> > +       switch (xperms->specified) {
> > +       case AVRULE_XPERMS_IOCTLDRIVER:
> > +       case AVRULE_XPERMS_IOCTLFUNCTION:
> > +               func = "ioctl";
> > +               break;
> > +       case AVRULE_XPERMS_NLMSG:
> > +               func = "nlmsg";
> > +               break;
> > +       default:
> > +               log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
> > +               goto exit;
> > +       }
> > +
> >         for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
> >                 cil_indent(indent);
> >                 cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
> > -                          "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
> > +                          func, pdb->p_class_val_to_name[classperm->tclass - 1]);
> >                 xperms_to_cil(xperms);
> >                 cil_printf(")))\n");
> >         }
> > diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
> > index 6826155c..f23cfd17 100644
> > --- a/libsepol/src/optimize.c
> > +++ b/libsepol/src/optimize.c
> > @@ -180,6 +180,12 @@ static int process_avtab_datum(uint16_t specified,
> >
> >                         if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
> >                                 return process_xperms(x1->perms, x2->perms);
> > +               } else if (x1->specified == AVTAB_XPERMS_NLMSG) {
> > +                       if (x2->specified == AVTAB_XPERMS_NLMSG) {
> > +                               if (x1->driver != x2->driver)
> > +                                       return 0;
> > +                               return process_xperms(x1->perms, x2->perms);
> > +                       }
> >                 }
> >                 return 0;
> >         }
> > diff --git a/libsepol/src/util.c b/libsepol/src/util.c
> > index 902c63c5..b5b41f87 100644
> > --- a/libsepol/src/util.c
> > +++ b/libsepol/src/util.c
> > @@ -124,7 +124,7 @@ char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
> >  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >  {
> >         uint16_t value;
> > -       uint16_t low_bit;
> > +       uint16_t low_bit = 0;
> >         uint16_t low_value;
> >         unsigned int bit;
> >         unsigned int in_range = 0;
> > @@ -135,7 +135,8 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >         p = xpermsbuf;
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                 return NULL;
> >
> >         len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");
> > @@ -173,6 +174,12 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >                                 len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
> >                         }
> >
> > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > +                       if (in_range) {
> > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
> > +                       } else {
> > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
> > +                       }
> >                 }
> >
> >                 if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))
> > --
> > 2.34.0.rc0.344.g81b53c2807-goog
> >
Jeffrey Vander Stoep Nov. 16, 2021, 9:17 a.m. UTC | #3
On Tue, Nov 16, 2021 at 10:11 AM Bram Bonné <brambonne@google.com> wrote:
>
> On Mon, Nov 15, 2021 at 9:08 PM James Carter <jwcart2@gmail.com> wrote:
> >
> > On Wed, Nov 10, 2021 at 6:12 AM Bram Bonne <brambonne@google.com> wrote:
> > >
> > > Reuse the existing extended permissions infrastructure to support
> > > sepolicy for different netlink message types.
> > >
> > > When individual netlink message types are omitted only the existing
> > > permissions are checked. As is the case for ioctl xperms, this feature
> > > is intended to provide finer granularity for nlmsg_read and nlmsg_write
> > > permissions, as they may be too imprecise. For example, a single
> > > NETLINK_ROUTE socket may provide access to both an interface's IP
> > > address and to its ARP table, which might have different privacy
> > > implications. In addition, the set of message types has grown over time,
> > > so even if the current list is acceptable, future additions might not be.
> > > It was suggested before on the mailing list [1] that extended permissions
> > > would be a good fit for this purpose.
> > >
> > > Existing policy using the nlmsg_read and nlmsg_write permissions will
> > > continue to work as-is. Similar to ioctl xperms, netlink xperms allow
> > > for a more fine-grained policy where needed.
> > >
> > > Example policy on Android, preventing regular apps from accessing the
> > > device's MAC address and ARP table, but allowing this access to
> > > privileged apps, looks as follows:
> > >
> > > allow netdomain self:netlink_route_socket {
> > >         create read getattr write setattr lock append connect getopt
> > >         setopt shutdown nlmsg_read
> > > };
> > > allowxperm netdomain self:netlink_route_socket nlmsg ~{
> > >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> >
> > Where are these defined? What are the valid values for userspace?
>
> These values need to be defined in sepolicy, similar to what is the
> case for ioctls. [2] has an example of such defines for Android.
>
> Kind regards,
> Bram
>
> [2] https://android-review.googlesource.com/c/platform/system/sepolicy/+/1786189/4/public/nlmsg_defines

This is how we currently handle individual ioctls on Android.

https://cs.android.com/android/platform/superproject/+/master:system/sepolicy/public/ioctl_defines?q=ioctl_defines
>
> >
> > Thanks,
> > Jim
> >
> >
> > > };
> > > allowxperm priv_app self:netlink_route_socket nlmsg {
> > >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> > > };
> > >
> > > Android currently uses code similar to [1] as a temporary workaround to
> > > limit access to certain netlink message types; our hope is that this patch
> > > will allow us to move back to upstream code with an approach that works for
> > > everyone.
> > >
> > > [1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/
> > >
> > > Signed-off-by: Bram Bonne <brambonne@google.com>
> > > ---
> > >  checkpolicy/policy_define.c                | 124 +++++++++++------
> > >  checkpolicy/test/dismod.c                  |   2 +
> > >  libsepol/cil/src/cil.c                     |   2 +
> > >  libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
> > >  libsepol/cil/src/cil_build_ast.c           |   4 +-
> > >  libsepol/cil/src/cil_internal.h            |   2 +
> > >  libsepol/cil/src/cil_policy.c              |   2 +
> > >  libsepol/cil/src/cil_verify.c              |   3 +
> > >  libsepol/cil/src/cil_write_ast.c           |  16 ++-
> > >  libsepol/include/sepol/policydb/avtab.h    |   1 +
> > >  libsepol/include/sepol/policydb/policydb.h |   1 +
> > >  libsepol/src/assertion.c                   |  15 +-
> > >  libsepol/src/expand.c                      |   3 +
> > >  libsepol/src/kernel_to_cil.c               |  23 ++-
> > >  libsepol/src/module_to_cil.c               |  29 +++-
> > >  libsepol/src/optimize.c                    |   6 +
> > >  libsepol/src/util.c                        |  11 +-
> > >  17 files changed, 321 insertions(+), 77 deletions(-)
> > >
> > > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > > index d3eb6111..8ca22993 100644
> > > --- a/checkpolicy/policy_define.c
> > > +++ b/checkpolicy/policy_define.c
> > > @@ -1818,27 +1818,27 @@ avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
> > >         return sl;
> > >  }
> > >
> > > -typedef struct av_ioctl_range {
> > > +typedef struct av_xperm_range {
> > >         uint16_t low;
> > >         uint16_t high;
> > > -} av_ioctl_range_t;
> > > +} av_xperm_range_t;
> > >
> > > -struct av_ioctl_range_list {
> > > +struct av_xperm_range_list {
> > >         uint8_t omit;
> > > -       av_ioctl_range_t range;
> > > -       struct av_ioctl_range_list *next;
> > > +       av_xperm_range_t range;
> > > +       struct av_xperm_range_list *next;
> > >  };
> > >
> > > -static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
> > > +static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
> > >  {
> > > -       struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
> > > +       struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
> > >
> > >         /* order list by range.low */
> > >         for (r = *rangehead; r != NULL; r = r->next) {
> > > -               sorted = malloc(sizeof(struct av_ioctl_range_list));
> > > +               sorted = malloc(sizeof(struct av_xperm_range_list));
> > >                 if (sorted == NULL)
> > >                         goto error;
> > > -               memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
> > > +               memcpy(sorted, r, sizeof(struct av_xperm_range_list));
> > >                 sorted->next = NULL;
> > >                 if (sortedhead == NULL) {
> > >                         sortedhead = sorted;
> > > @@ -1877,9 +1877,9 @@ error:
> > >         return -1;
> > >  }
> > >
> > > -static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> > > +static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
> > >  {
> > > -       struct av_ioctl_range_list *r, *tmp;
> > > +       struct av_xperm_range_list *r, *tmp;
> > >         r = *rangehead;
> > >         while (r != NULL && r->next != NULL) {
> > >                 /* merge */
> > > @@ -1897,15 +1897,15 @@ static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> > >         return 0;
> > >  }
> > >
> > > -static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> > > +static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
> > >  {
> > >         char *id;
> > > -       struct av_ioctl_range_list *rnew, *r = NULL;
> > > +       struct av_xperm_range_list *rnew, *r = NULL;
> > >         uint8_t omit = 0;
> > >
> > >         *rangehead = NULL;
> > >
> > > -       /* read in all the ioctl commands */
> > > +       /* read in all the netlink ranges / ioctl commands */
> > >         while ((id = queue_remove(id_queue))) {
> > >                 if (strcmp(id,"~") == 0) {
> > >                         /* these are values to be omitted */
> > > @@ -1917,13 +1917,13 @@ static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> > >                         id = queue_remove(id_queue);
> > >                         r->range.high = (uint16_t) strtoul(id,NULL,0);
> > >                         if (r->range.high < r->range.low) {
> > > -                               yyerror("Ioctl ranges must be in ascending order.");
> > > +                               yyerror("xperm ranges must be in ascending order.");
> > >                                 return -1;
> > >                         }
> > >                         free(id);
> > >                 } else {
> > >                         /* read in new low value */
> > > -                       rnew = malloc(sizeof(struct av_ioctl_range_list));
> > > +                       rnew = malloc(sizeof(struct av_xperm_range_list));
> > >                         if (rnew == NULL)
> > >                                 goto error;
> > >                         rnew->next = NULL;
> > > @@ -1950,11 +1950,11 @@ error:
> > >  }
> > >
> > >  /* flip to included ranges */
> > > -static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> > > +static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
> > >  {
> > > -       struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
> > > +       struct av_xperm_range_list *rnew, *r, *newhead, *r2;
> > >
> > > -       rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > > +       rnew = calloc(1, sizeof(struct av_xperm_range_list));
> > >         if (!rnew)
> > >                 goto error;
> > >
> > > @@ -1972,7 +1972,7 @@ static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> > >
> > >         while (r) {
> > >                 r2->range.high = r->range.low - 1;
> > > -               rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > > +               rnew = calloc(1, sizeof(struct av_xperm_range_list));
> > >                 if (!rnew)
> > >                         goto error;
> > >                 r2->next = rnew;
> > > @@ -1998,27 +1998,27 @@ error:
> > >         return -1;
> > >  }
> > >
> > > -static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
> > > +static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
> > >  {
> > > -       struct av_ioctl_range_list *rangehead;
> > > +       struct av_xperm_range_list *rangehead;
> > >         uint8_t omit;
> > >
> > >         /* read in ranges to include and omit */
> > > -       if (avrule_read_ioctls(&rangehead))
> > > +       if (avrule_read_xperm_ranges(&rangehead))
> > >                 return -1;
> > >         if (rangehead == NULL) {
> > > -               yyerror("error processing ioctl commands");
> > > +               yyerror("error processing ioctl/netlink commands");
> > >                 return -1;
> > >         }
> > >         omit = rangehead->omit;
> > > -       /* sort and merge the input ioctls */
> > > -       if (avrule_sort_ioctls(&rangehead))
> > > +       /* sort and merge the input ranges */
> > > +       if (avrule_sort_xperms(&rangehead))
> > >                 return -1;
> > > -       if (avrule_merge_ioctls(&rangehead))
> > > +       if (avrule_merge_xperms(&rangehead))
> > >                 return -1;
> > >         /* flip ranges if these are omitted */
> > >         if (omit) {
> > > -               if (avrule_omit_ioctls(&rangehead))
> > > +               if (avrule_omit_xperms(&rangehead))
> > >                         return -1;
> > >         }
> > >
> > > @@ -2189,11 +2189,11 @@ static int avrule_xperms_used(const av_extended_perms_t *xperms)
> > >  #define IOC_DRIV(x) ((x) >> 8)
> > >  #define IOC_FUNC(x) ((x) & 0xff)
> > >  #define IOC_CMD(driver, func) (((driver) << 8) + (func))
> > > -static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> > > +static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
> > >                                 av_extended_perms_t *complete_driver,
> > >                                 av_extended_perms_t **extended_perms)
> > >  {
> > > -       struct av_ioctl_range_list *r;
> > > +       struct av_xperm_range_list *r;
> > >         av_extended_perms_t *xperms;
> > >         uint8_t low, high;
> > >
> > > @@ -2228,10 +2228,10 @@ static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> > >
> > >  }
> > >
> > > -static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> > > +static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
> > >                         av_extended_perms_t **extended_perms)
> > >  {
> > > -       struct av_ioctl_range_list *r;
> > > +       struct av_xperm_range_list *r;
> > >         av_extended_perms_t *xperms;
> > >         uint16_t low, high;
> > >         xperms = calloc(1, sizeof(av_extended_perms_t));
> > > @@ -2270,10 +2270,10 @@ static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> > >         return 0;
> > >  }
> > >
> > > -static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> > > +static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
> > >                 av_extended_perms_t **extended_perms, unsigned int driver)
> > >  {
> > > -       struct av_ioctl_range_list *r;
> > > +       struct av_xperm_range_list *r;
> > >         av_extended_perms_t *xperms;
> > >         uint16_t low, high;
> > >
> > > @@ -2307,7 +2307,6 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> > >                 high = IOC_FUNC(high);
> > >                 avrule_xperm_setrangebits(low, high, xperms);
> > >                 xperms->driver = driver;
> > > -               xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> > >                 r = r->next;
> > >         }
> > >
> > > @@ -2320,6 +2319,18 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> > >         return 0;
> > >  }
> > >
> > > +void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
> > > +{
> > > +       struct av_xperm_range_list *r, *tmp;
> > > +
> > > +       r = rangelist;
> > > +       while (r) {
> > > +               tmp = r;
> > > +               r = r->next;
> > > +               free(tmp);
> > > +       }
> > > +}
> > > +
> > >  static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
> > >  {
> > >         unsigned int i;
> > > @@ -2384,13 +2395,13 @@ static int avrule_cpy(avrule_t *dest, const avrule_t *src)
> > >  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> > >  {
> > >         avrule_t *avrule;
> > > -       struct av_ioctl_range_list *rangelist, *r;
> > > +       struct av_xperm_range_list *rangelist, *r;
> > >         av_extended_perms_t *complete_driver, *partial_driver, *xperms;
> > >         unsigned int i;
> > >
> > >
> > >         /* organize ioctl ranges */
> > > -       if (avrule_ioctl_ranges(&rangelist))
> > > +       if (avrule_xperm_ranges(&rangelist))
> > >                 return -1;
> > >
> > >         /* create rule for ioctl driver types that are entirely enabled */
> > > @@ -2422,10 +2433,11 @@ static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> > >          */
> > >         i = 0;
> > >         while (xperms_for_each_bit(&i, partial_driver)) {
> > > -               if (avrule_ioctl_func(rangelist, &xperms, i))
> > > +               if (avrule_xperms_single_driver(rangelist, &xperms, i))
> > >                         return -1;
> > >
> > >                 if (xperms) {
> > > +                       xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> > >                         avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> > >                         if (!avrule) {
> > >                                 yyerror("out of memory");
> > > @@ -2451,6 +2463,38 @@ done:
> > >         return 0;
> > >  }
> > >
> > > +int define_te_avtab_netlink(avrule_t *avrule_template)
> > > +{
> > > +       avrule_t *avrule;
> > > +       struct av_xperm_range_list *range_list;
> > > +       av_extended_perms_t *xperms = NULL;
> > > +
> > > +       /* organize message ranges */
> > > +       if (avrule_xperm_ranges(&range_list))
> > > +               return -1;
> > > +
> > > +       /* Netlink message types comfortably fit into a single driver
> > > +        * (see RTM_MAX in uapi/linux/rtnetlink.h)
> > > +        */
> > > +       avrule_xperms_single_driver(range_list, &xperms, 0);
> > > +
> > > +       if (xperms && avrule_xperms_used(xperms)) {
> > > +               xperms->specified = AVRULE_XPERMS_NLMSG;
> > > +               avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> > > +               if (!avrule) {
> > > +                       yyerror("out of memory");
> > > +                       return -1;
> > > +               }
> > > +               if (avrule_cpy(avrule, avrule_template))
> > > +                       return -1;
> > > +               avrule->xperms = xperms;
> > > +               append_avrule(avrule);
> > > +       } else {
> > > +               free(xperms);
> > > +       }
> > > +       return 0;
> > > +}
> > > +
> > >  int define_te_avtab_extended_perms(int which)
> > >  {
> > >         char *id;
> > > @@ -2473,8 +2517,10 @@ int define_te_avtab_extended_perms(int which)
> > >         id = queue_remove(id_queue);
> > >         if (strcmp(id,"ioctl") == 0) {
> > >                 rc = define_te_avtab_ioctl(avrule_template);
> > > +       } else if (strcmp(id, "nlmsg") == 0) {
> > > +               rc = define_te_avtab_netlink(avrule_template);
> > >         } else {
> > > -               yyerror("only ioctl extended permissions are supported");
> > > +               yyerror("only ioctl / nlmsg extended permissions are supported");
> > >                 rc = -1;
> > >         }
> > >
> > > diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> > > index ec2a3e9a..f8652ec5 100644
> > > --- a/checkpolicy/test/dismod.c
> > > +++ b/checkpolicy/test/dismod.c
> > > @@ -296,6 +296,8 @@ static int display_avrule(avrule_t * avrule, policydb_t * policy,
> > >                         xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
> > >                 else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
> > >                         xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
> > > +               else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
> > > +                       xperms.specified = AVTAB_XPERMS_NLMSG;
> > >                 else {
> > >                         fprintf(fp, "     ERROR: no valid xperms specified\n");
> > >                         return -1;
> > > diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> > > index 4cc7f87f..fbdb005a 100644
> > > --- a/libsepol/cil/src/cil.c
> > > +++ b/libsepol/cil/src/cil.c
> > > @@ -219,6 +219,7 @@ char *CIL_KEY_DONTAUDITX;
> > >  char *CIL_KEY_NEVERALLOWX;
> > >  char *CIL_KEY_PERMISSIONX;
> > >  char *CIL_KEY_IOCTL;
> > > +char *CIL_KEY_NLMSG;
> > >  char *CIL_KEY_UNORDERED;
> > >  char *CIL_KEY_SRC_INFO;
> > >  char *CIL_KEY_SRC_CIL;
> > > @@ -388,6 +389,7 @@ static void cil_init_keys(void)
> > >         CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
> > >         CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
> > >         CIL_KEY_IOCTL = cil_strpool_add("ioctl");
> > > +       CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
> > >         CIL_KEY_UNORDERED = cil_strpool_add("unordered");
> > >         CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
> > >         CIL_KEY_SRC_CIL = cil_strpool_add("cil");
> > > diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> > > index d8aa495a..f9938d66 100644
> > > --- a/libsepol/cil/src/cil_binary.c
> > > +++ b/libsepol/cil/src/cil_binary.c
> > > @@ -66,6 +66,7 @@ struct cil_args_binary {
> > >         int pass;
> > >         hashtab_t role_trans_table;
> > >         hashtab_t avrulex_ioctl_table;
> > > +       hashtab_t avrulex_nlmsg_table;
> > >         void **type_value_to_cil;
> > >  };
> > >
> > > @@ -1553,7 +1554,7 @@ void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
> > >  #define IOC_DRIV(x) (x >> 8)
> > >  #define IOC_FUNC(x) (x & 0xff)
> > >
> > > -int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > > +int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > >  {
> > >         ebitmap_node_t *node;
> > >         unsigned int i;
> > > @@ -1618,13 +1619,53 @@ int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
> > >         return SEPOL_OK;
> > >  }
> > >
> > > -int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > > +int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > > +{
> > > +       ebitmap_node_t *node;
> > > +       uint16_t i;
> > > +       uint16_t low;
> > > +       struct avtab_extended_perms *avtab = NULL;
> > > +       int start_new_range = 1;
> > > +
> > > +       cil_list_init(xperms_list, CIL_NONE);
> > > +
> > > +       ebitmap_for_each_positive_bit(xperms, node, i) {
> > > +               if (start_new_range) {
> > > +                       low = i;
> > > +                       start_new_range = 0;
> > > +               }
> > > +
> > > +               // Continue if the current bit isn't the end of the driver range
> > > +               // or the next bit is set
> > > +               if (ebitmap_get_bit(xperms, i + 1)) {
> > > +                       continue;
> > > +               }
> > > +
> > > +               start_new_range = 1;
> > > +
> > > +               if (!avtab) {
> > > +                       avtab = cil_calloc(1, sizeof(*avtab));
> > > +                       // Netlink message types all fit in driver 0
> > > +                       avtab->driver = 0;
> > > +                       avtab->specified = AVTAB_XPERMS_NLMSG;
> > > +               }
> > > +
> > > +               __avrule_xperm_setrangebits(low, i, avtab);
> > > +       }
> > > +
> > > +       if (avtab) {
> > > +               cil_list_append(*xperms_list, CIL_NONE, avtab);
> > > +       }
> > > +
> > > +       return SEPOL_OK;
> > > +}
> > > +
> > > +int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
> > >  {
> > >         int rc = SEPOL_OK;
> > >         struct policydb *pdb;
> > >         avtab_key_t *avtab_key;
> > >         avtab_datum_t avtab_datum;
> > > -       struct cil_list *xperms_list = NULL;
> > >         struct cil_list_item *item;
> > >         class_datum_t *sepol_obj;
> > >         uint32_t data = 0;
> > > @@ -1637,17 +1678,12 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> > >         // setting the data for an extended avtab isn't really necessary because
> > >         // it is ignored by the kernel. However, neverallow checking requires that
> > >         // the data value be set, so set it for that to work.
> > > -       rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
> > > +       rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
> > >         if (rc != SEPOL_OK) {
> > >                 goto exit;
> > >         }
> > >         avtab_datum.data = data;
> > >
> > > -       rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > > -       if (rc != SEPOL_OK) {
> > > -               goto exit;
> > > -       }
> > > -
> > >         cil_list_for_each(item, xperms_list) {
> > >                 avtab_datum.xperms = item->data;
> > >                 rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
> > > @@ -1659,16 +1695,57 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> > >         rc = SEPOL_OK;
> > >
> > >  exit:
> > > +       return rc;
> > > +}
> > > +
> > > +void __cil_cleanup_xperms(struct cil_list *xperms_list)
> > > +{
> > > +       struct cil_list_item *item;
> > > +
> > >         if (xperms_list != NULL) {
> > >                 cil_list_for_each(item, xperms_list) {
> > >                         free(item->data);
> > > +                       item->data = NULL;
> > >                 }
> > >                 cil_list_destroy(&xperms_list, CIL_FALSE);
> > >         }
> > > +}
> > > +
> > > +int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > > +{
> > > +       struct cil_list *xperms_list = NULL;
> > > +       int rc;
> > > +
> > > +       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > > +       if (rc != SEPOL_OK) {
> > > +               goto exit;
> > > +       }
> > > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
> > > +
> > > +exit:
> > > +       __cil_cleanup_xperms(xperms_list);
> > > +
> > >         return rc;
> > >  }
> > >
> > > -int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> > > +int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > > +{
> > > +       struct cil_list *xperms_list = NULL;
> > > +       int rc;
> > > +
> > > +       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > > +       if (rc != SEPOL_OK) {
> > > +               goto exit;
> > > +       }
> > > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
> > > +
> > > +exit:
> > > +       __cil_cleanup_xperms(xperms_list);
> > > +
> > > +       return rc;
> > > +}
> > > +
> > > +int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> > >  {
> > >         uint16_t specified;
> > >         avtab_key_t *avtab_key;
> > > @@ -1747,8 +1824,12 @@ int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
> > >                 if (rc != SEPOL_OK) goto exit;
> > >
> > >                 switch (permx->kind) {
> > > -               case  CIL_PERMX_KIND_IOCTL:
> > > -                       rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > > +               case CIL_PERMX_KIND_IOCTL:
> > > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > > +                       if (rc != SEPOL_OK) goto exit;
> > > +                       break;
> > > +               case CIL_PERMX_KIND_NLMSG:
> > > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > >                         if (rc != SEPOL_OK) goto exit;
> > >                         break;
> > >                 default:
> > > @@ -4417,6 +4498,9 @@ static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
> > >                         case CIL_PERMX_KIND_IOCTL:
> > >                                 perm_str = CIL_KEY_IOCTL;
> > >                                 break;
> > > +                       case CIL_PERMX_KIND_NLMSG:
> > > +                               perm_str = CIL_KEY_NLMSG;
> > > +                               break;
> > >                         default:
> > >                                 rc = SEPOL_ERR;
> > >                                 goto exit;
> > > @@ -4563,6 +4647,9 @@ static void __cil_print_permissionx(struct cil_permissionx *px)
> > >                 case CIL_PERMX_KIND_IOCTL:
> > >                         kind_str = CIL_KEY_IOCTL;
> > >                         break;
> > > +               case CIL_PERMX_KIND_NLMSG:
> > > +                       kind_str = CIL_KEY_NLMSG;
> > > +                       break;
> > >                 default:
> > >                         kind_str = "unknown";
> > >                         break;
> > > @@ -4696,8 +4783,21 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> > >                         goto exit;
> > >                 }
> > >
> > > -               rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > > -               if (rc != SEPOL_OK) {
> > > +               switch (cil_rule->perms.x.permx->kind) {
> > > +               case CIL_PERMX_KIND_IOCTL:
> > > +                       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > > +                       if (rc != SEPOL_OK) {
> > > +                               goto exit;
> > > +                       }
> > > +                       break;
> > > +               case CIL_PERMX_KIND_NLMSG:
> > > +                       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > > +                       if (rc != SEPOL_OK) {
> > > +                               goto exit;
> > > +                       }
> > > +                       break;
> > > +               default:
> > > +                       rc = SEPOL_ERR;
> > >                         goto exit;
> > >                 }
> > >
> > > @@ -4715,13 +4815,7 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> > >         }
> > >
> > >  exit:
> > > -       if (xperms != NULL) {
> > > -               cil_list_for_each(item, xperms) {
> > > -                       free(item->data);
> > > -                       item->data = NULL;
> > > -               }
> > > -               cil_list_destroy(&xperms, CIL_FALSE);
> > > -       }
> > > +       __cil_cleanup_xperms(xperms);
> > >
> > >         rule->xperms = NULL;
> > >         __cil_destroy_sepol_avrules(rule);
> > > @@ -4904,6 +4998,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> > >         struct cil_list *neverallows = NULL;
> > >         hashtab_t role_trans_table = NULL;
> > >         hashtab_t avrulex_ioctl_table = NULL;
> > > +       hashtab_t avrulex_nlmsg_table = NULL;
> > >         void **type_value_to_cil = NULL;
> > >         struct cil_class **class_value_to_cil = NULL;
> > >         struct cil_perm ***perm_value_to_cil = NULL;
> > > @@ -4947,7 +5042,13 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> > >
> > >         avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> > >         if (!avrulex_ioctl_table) {
> > > -               cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
> > > +               cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
> > > +               goto exit;
> > > +       }
> > > +
> > > +       avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> > > +       if (!avrulex_nlmsg_table) {
> > > +               cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
> > >                 goto exit;
> > >         }
> > >
> > > @@ -4958,6 +5059,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> > >         extra_args.neverallows = neverallows;
> > >         extra_args.role_trans_table = role_trans_table;
> > >         extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
> > > +       extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
> > >         extra_args.type_value_to_cil = type_value_to_cil;
> > >
> > >         for (i = 1; i <= 3; i++) {
> > > @@ -4980,7 +5082,12 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> > >                 if (i == 3) {
> > >                         rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
> > >                         if (rc != SEPOL_OK) {
> > > -                               cil_log(CIL_INFO, "Failure creating avrulex rules\n");
> > > +                               cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
> > > +                               goto exit;
> > > +                       }
> > > +                       rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
> > > +                       if (rc != SEPOL_OK) {
> > > +                               cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
> > >                                 goto exit;
> > >                         }
> > >                 }
> > > @@ -5056,6 +5163,7 @@ exit:
> > >         hashtab_destroy(role_trans_table);
> > >         hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
> > >         hashtab_destroy(avrulex_ioctl_table);
> > > +       hashtab_destroy(avrulex_nlmsg_table);
> > >         free(type_value_to_cil);
> > >         free(class_value_to_cil);
> > >         if (perm_value_to_cil != NULL) {
> > > diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> > > index 9c34be23..b1cfb4f0 100644
> > > --- a/libsepol/cil/src/cil_build_ast.c
> > > +++ b/libsepol/cil/src/cil_build_ast.c
> > > @@ -2159,8 +2159,10 @@ int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
> > >
> > >         if (parse_current->data == CIL_KEY_IOCTL) {
> > >                 permx->kind = CIL_PERMX_KIND_IOCTL;
> > > +       } else if (parse_current->data == CIL_KEY_NLMSG) {
> > > +               permx->kind = CIL_PERMX_KIND_NLMSG;
> > >         } else {
> > > -               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
> > > +               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
> > >                 rc = SEPOL_ERR;
> > >                 goto exit;
> > >         }
> > > diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> > > index 6f1d3cb5..c968cf1e 100644
> > > --- a/libsepol/cil/src/cil_internal.h
> > > +++ b/libsepol/cil/src/cil_internal.h
> > > @@ -236,6 +236,7 @@ extern char *CIL_KEY_DONTAUDITX;
> > >  extern char *CIL_KEY_NEVERALLOWX;
> > >  extern char *CIL_KEY_PERMISSIONX;
> > >  extern char *CIL_KEY_IOCTL;
> > > +extern char *CIL_KEY_NLMSG;
> > >  extern char *CIL_KEY_UNORDERED;
> > >  extern char *CIL_KEY_SRC_INFO;
> > >  extern char *CIL_KEY_SRC_CIL;
> > > @@ -623,6 +624,7 @@ struct cil_avrule {
> > >  };
> > >
> > >  #define CIL_PERMX_KIND_IOCTL 1
> > > +#define CIL_PERMX_KIND_NLMSG 2
> > >  struct cil_permissionx {
> > >         struct cil_symtab_datum datum;
> > >         uint32_t kind;
> > > diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> > > index 7c543c47..2703d764 100644
> > > --- a/libsepol/cil/src/cil_policy.c
> > > +++ b/libsepol/cil/src/cil_policy.c
> > > @@ -1112,6 +1112,8 @@ static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
> > >
> > >         if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > >                 kind = "ioctl";
> > > +       } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > > +               kind = CIL_KEY_NLMSG;
> > >         } else {
> > >                 kind = "???";
> > >         }
> > > diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
> > > index d994d717..67bdb0e9 100644
> > > --- a/libsepol/cil/src/cil_verify.c
> > > +++ b/libsepol/cil/src/cil_verify.c
> > > @@ -1427,6 +1427,9 @@ int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
> > >                 case CIL_PERMX_KIND_IOCTL:
> > >                         kind_str = CIL_KEY_IOCTL;
> > >                         break;
> > > +               case CIL_PERMX_KIND_NLMSG:
> > > +                       kind_str = CIL_KEY_NLMSG;
> > > +                       break;
> > >                 default:
> > >                         cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
> > >                         rc = SEPOL_ERR;
> > > diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> > > index d7f00bcc..b93a4fa1 100644
> > > --- a/libsepol/cil/src/cil_write_ast.c
> > > +++ b/libsepol/cil/src/cil_write_ast.c
> > > @@ -303,7 +303,13 @@ static void write_permx(FILE *out, struct cil_permissionx *permx)
> > >                 fprintf(out, "%s", datum_to_str(DATUM(permx)));
> > >         } else {
> > >                 fprintf(out, "(");
> > > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > > +                       fprintf(out, "%s ", "ioctl");
> > > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > > +                       fprintf(out, "%s ", "nlmsg");
> > > +               } else {
> > > +                       fprintf(out, "%s ", "<?KIND>");
> > > +               }
> > >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> > >                 write_expr(out, permx->expr_str);
> > >                 fprintf(out, ")");
> > > @@ -812,7 +818,13 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
> > >         case CIL_PERMISSIONX: {
> > >                 struct cil_permissionx *permx = node->data;
> > >                 fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
> > > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > > +                       fprintf(out, "%s ", "ioctl");
> > > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > > +                       fprintf(out, "%s ", "nlmsg");
> > > +               } else {
> > > +                       fprintf(out, "%s ", "<?KIND>");
> > > +               }
> > >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> > >                 write_expr(out, permx->expr_str);
> > >                 fprintf(out, "))\n");
> > > diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
> > > index 10ecde9a..aa7481d3 100644
> > > --- a/libsepol/include/sepol/policydb/avtab.h
> > > +++ b/libsepol/include/sepol/policydb/avtab.h
> > > @@ -74,6 +74,7 @@ typedef struct avtab_extended_perms {
> > >
> > >  #define AVTAB_XPERMS_IOCTLFUNCTION     0x01
> > >  #define AVTAB_XPERMS_IOCTLDRIVER       0x02
> > > +#define AVTAB_XPERMS_NLMSG             0x03
> > >         /* extension of the avtab_key specified */
> > >         uint8_t specified;
> > >         uint8_t driver;
> > > diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> > > index 4bf9f05d..55918e1d 100644
> > > --- a/libsepol/include/sepol/policydb/policydb.h
> > > +++ b/libsepol/include/sepol/policydb/policydb.h
> > > @@ -259,6 +259,7 @@ typedef struct class_perm_node {
> > >  typedef struct av_extended_perms {
> > >  #define AVRULE_XPERMS_IOCTLFUNCTION    0x01
> > >  #define AVRULE_XPERMS_IOCTLDRIVER      0x02
> > > +#define AVRULE_XPERMS_NLMSG            0x03
> > >         uint8_t specified;
> > >         uint8_t driver;
> > >         /* 256 bits of permissions */
> > > diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
> > > index dd2749a0..47e45713 100644
> > > --- a/libsepol/src/assertion.c
> > > +++ b/libsepol/src/assertion.c
> > > @@ -101,6 +101,9 @@ static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
> > >         } else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
> > >                         && (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
> > >                 rc = extended_permissions_and(neverallow->perms, allow->perms);
> > > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > > +               rc = extended_permissions_and(neverallow->perms, allow->perms);
> > >         }
> > >
> > >         return rc;
> > > @@ -133,6 +136,12 @@ static void extended_permissions_violated(avtab_extended_perms_t *result,
> > >                 result->specified = AVTAB_XPERMS_IOCTLDRIVER;
> > >                 for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> > >                         result->perms[i] = neverallow->perms[i] & allow->perms[i];
> > > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > > +               result->specified = AVTAB_XPERMS_NLMSG;
> > > +               result->driver = 0;
> > > +               for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> > > +                       result->perms[i] = neverallow->perms[i] & allow->perms[i];
> > >         }
> > >  }
> > >
> > > @@ -166,7 +175,8 @@ static int report_assertion_extended_permissions(sepol_handle_t *handle,
> > >                              node = avtab_search_node_next(node, tmp_key.specified)) {
> > >                                 xperms = node->datum.xperms;
> > >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> > >                                         continue;
> > >
> > >                                 rc = check_extended_permissions(avrule->xperms, xperms);
> > > @@ -346,7 +356,8 @@ static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
> > >                                 xperms = node->datum.xperms;
> > >
> > >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> > >                                         continue;
> > >                                 rc = check_extended_permissions(neverallow_xperms, xperms);
> > >                                 if (rc)
> > > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > > index a6a466f7..959e6eb2 100644
> > > --- a/libsepol/src/expand.c
> > > +++ b/libsepol/src/expand.c
> > > @@ -1796,6 +1796,9 @@ static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
> > >         case AVRULE_XPERMS_IOCTLDRIVER:
> > >                 xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
> > >                 break;
> > > +       case AVRULE_XPERMS_NLMSG:
> > > +               xperms->specified = AVTAB_XPERMS_NLMSG;
> > > +               break;
> > >         default:
> > >                 return -1;
> > >         }
> > > diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> > > index 305567a5..090d4e43 100644
> > > --- a/libsepol/src/kernel_to_cil.c
> > > +++ b/libsepol/src/kernel_to_cil.c
> > > @@ -1617,7 +1617,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> > >         remaining = sizeof(xpermsbuf);
> > >
> > >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
> > > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > > +               && (xperms->specified != AVTAB_XPERMS_NLMSG)) {
> > >                 return NULL;
> > >         }
> > >
> > > @@ -1637,7 +1638,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> > >                         continue;
> > >                 }
> > >
> > > -               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
> > > +               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
> > > +                       || xperms->specified & AVTAB_XPERMS_NLMSG) {
> > >                         value = xperms->driver<<8 | bit;
> > >                         if (in_range) {
> > >                                 low_value = xperms->driver<<8 | low_bit;
> > > @@ -1679,7 +1681,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> > >  {
> > >         uint32_t data = datum->data;
> > >         type_datum_t *type;
> > > -       const char *flavor, *tgt;
> > > +       const char *flavor, *tgt, *func;
> > >         char *src, *class, *perms, *new;
> > >         char *rule = NULL;
> > >
> > > @@ -1742,8 +1744,21 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> > >                         goto exit;
> > >                 }
> > >
> > > +               switch (datum->xperms->specified) {
> > > +               case AVRULE_XPERMS_IOCTLDRIVER:
> > > +               case AVRULE_XPERMS_IOCTLFUNCTION:
> > > +                       func = "ioctl";
> > > +                       break;
> > > +               case AVRULE_XPERMS_NLMSG:
> > > +                       func = "nlmsg";
> > > +                       break;
> > > +               default:
> > > +                       sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
> > > +                       goto exit;
> > > +               }
> > > +
> > >                 rule = create_str("(%s %s %s (%s %s (%s)))", 6,
> > > -                                 flavor, src, tgt, "ioctl", class, perms);
> > > +                                 flavor, src, tgt, func, class, perms);
> > >         } else {
> > >                 new = pdb->p_type_val_to_name[data - 1];
> > >
> > > diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> > > index 16e4004e..47d9b171 100644
> > > --- a/libsepol/src/module_to_cil.c
> > > +++ b/libsepol/src/module_to_cil.c
> > > @@ -627,14 +627,15 @@ exit:
> > >  static int xperms_to_cil(const av_extended_perms_t *xperms)
> > >  {
> > >         uint16_t value;
> > > -       uint16_t low_bit;
> > > +       uint16_t low_bit = 0;
> > >         uint16_t low_value;
> > >         unsigned int bit;
> > >         unsigned int in_range = 0;
> > >         int first = 1;
> > >
> > >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> > >                 return -1;
> > >
> > >         for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
> > > @@ -674,6 +675,13 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> > >                         } else {
> > >                                 cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
> > >                         }
> > > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > > +                       if (in_range) {
> > > +                               cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
> > > +                               in_range = 0;
> > > +                       } else {
> > > +                               cil_printf("0x%hx", (uint16_t) bit);
> > > +                       }
> > >                 }
> > >         }
> > >
> > > @@ -683,7 +691,7 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> > >  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
> > >  {
> > >         int rc = -1;
> > > -       const char *rule;
> > > +       const char *rule, *func;
> > >         const struct class_perm_node *classperm;
> > >
> > >         switch (type) {
> > > @@ -705,10 +713,23 @@ static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
> > >                 goto exit;
> > >         }
> > >
> > > +       switch (xperms->specified) {
> > > +       case AVRULE_XPERMS_IOCTLDRIVER:
> > > +       case AVRULE_XPERMS_IOCTLFUNCTION:
> > > +               func = "ioctl";
> > > +               break;
> > > +       case AVRULE_XPERMS_NLMSG:
> > > +               func = "nlmsg";
> > > +               break;
> > > +       default:
> > > +               log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
> > > +               goto exit;
> > > +       }
> > > +
> > >         for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
> > >                 cil_indent(indent);
> > >                 cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
> > > -                          "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
> > > +                          func, pdb->p_class_val_to_name[classperm->tclass - 1]);
> > >                 xperms_to_cil(xperms);
> > >                 cil_printf(")))\n");
> > >         }
> > > diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
> > > index 6826155c..f23cfd17 100644
> > > --- a/libsepol/src/optimize.c
> > > +++ b/libsepol/src/optimize.c
> > > @@ -180,6 +180,12 @@ static int process_avtab_datum(uint16_t specified,
> > >
> > >                         if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
> > >                                 return process_xperms(x1->perms, x2->perms);
> > > +               } else if (x1->specified == AVTAB_XPERMS_NLMSG) {
> > > +                       if (x2->specified == AVTAB_XPERMS_NLMSG) {
> > > +                               if (x1->driver != x2->driver)
> > > +                                       return 0;
> > > +                               return process_xperms(x1->perms, x2->perms);
> > > +                       }
> > >                 }
> > >                 return 0;
> > >         }
> > > diff --git a/libsepol/src/util.c b/libsepol/src/util.c
> > > index 902c63c5..b5b41f87 100644
> > > --- a/libsepol/src/util.c
> > > +++ b/libsepol/src/util.c
> > > @@ -124,7 +124,7 @@ char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
> > >  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> > >  {
> > >         uint16_t value;
> > > -       uint16_t low_bit;
> > > +       uint16_t low_bit = 0;
> > >         uint16_t low_value;
> > >         unsigned int bit;
> > >         unsigned int in_range = 0;
> > > @@ -135,7 +135,8 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> > >         p = xpermsbuf;
> > >
> > >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> > >                 return NULL;
> > >
> > >         len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");
> > > @@ -173,6 +174,12 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> > >                                 len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
> > >                         }
> > >
> > > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > > +                       if (in_range) {
> > > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
> > > +                       } else {
> > > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
> > > +                       }
> > >                 }
> > >
> > >                 if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))
> > > --
> > > 2.34.0.rc0.344.g81b53c2807-goog
> > >
James Carter Nov. 16, 2021, 3:44 p.m. UTC | #4
On Wed, Nov 10, 2021 at 6:12 AM Bram Bonne <brambonne@google.com> wrote:
>
> Reuse the existing extended permissions infrastructure to support
> sepolicy for different netlink message types.
>
> When individual netlink message types are omitted only the existing
> permissions are checked. As is the case for ioctl xperms, this feature
> is intended to provide finer granularity for nlmsg_read and nlmsg_write
> permissions, as they may be too imprecise. For example, a single
> NETLINK_ROUTE socket may provide access to both an interface's IP
> address and to its ARP table, which might have different privacy
> implications. In addition, the set of message types has grown over time,
> so even if the current list is acceptable, future additions might not be.
> It was suggested before on the mailing list [1] that extended permissions
> would be a good fit for this purpose.
>
> Existing policy using the nlmsg_read and nlmsg_write permissions will
> continue to work as-is. Similar to ioctl xperms, netlink xperms allow
> for a more fine-grained policy where needed.
>
> Example policy on Android, preventing regular apps from accessing the
> device's MAC address and ARP table, but allowing this access to
> privileged apps, looks as follows:
>
> allow netdomain self:netlink_route_socket {
>         create read getattr write setattr lock append connect getopt
>         setopt shutdown nlmsg_read
> };
> allowxperm netdomain self:netlink_route_socket nlmsg ~{
>         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> };
> allowxperm priv_app self:netlink_route_socket nlmsg {
>         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> };
>
> Android currently uses code similar to [1] as a temporary workaround to
> limit access to certain netlink message types; our hope is that this patch
> will allow us to move back to upstream code with an approach that works for
> everyone.
>
> [1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/
>
> Signed-off-by: Bram Bonne <brambonne@google.com>
> ---
>  checkpolicy/policy_define.c                | 124 +++++++++++------
>  checkpolicy/test/dismod.c                  |   2 +
>  libsepol/cil/src/cil.c                     |   2 +
>  libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
>  libsepol/cil/src/cil_build_ast.c           |   4 +-
>  libsepol/cil/src/cil_internal.h            |   2 +
>  libsepol/cil/src/cil_policy.c              |   2 +
>  libsepol/cil/src/cil_verify.c              |   3 +
>  libsepol/cil/src/cil_write_ast.c           |  16 ++-
>  libsepol/include/sepol/policydb/avtab.h    |   1 +
>  libsepol/include/sepol/policydb/policydb.h |   1 +
>  libsepol/src/assertion.c                   |  15 +-
>  libsepol/src/expand.c                      |   3 +
>  libsepol/src/kernel_to_cil.c               |  23 ++-
>  libsepol/src/module_to_cil.c               |  29 +++-
>  libsepol/src/optimize.c                    |   6 +
>  libsepol/src/util.c                        |  11 +-
>  17 files changed, 321 insertions(+), 77 deletions(-)
>
> diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> index d3eb6111..8ca22993 100644
> --- a/checkpolicy/policy_define.c
> +++ b/checkpolicy/policy_define.c
> @@ -1818,27 +1818,27 @@ avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
>         return sl;
>  }
>
> -typedef struct av_ioctl_range {
> +typedef struct av_xperm_range {
>         uint16_t low;
>         uint16_t high;
> -} av_ioctl_range_t;
> +} av_xperm_range_t;
>
> -struct av_ioctl_range_list {
> +struct av_xperm_range_list {
>         uint8_t omit;
> -       av_ioctl_range_t range;
> -       struct av_ioctl_range_list *next;
> +       av_xperm_range_t range;
> +       struct av_xperm_range_list *next;
>  };
>
> -static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
> +       struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
>
>         /* order list by range.low */
>         for (r = *rangehead; r != NULL; r = r->next) {
> -               sorted = malloc(sizeof(struct av_ioctl_range_list));
> +               sorted = malloc(sizeof(struct av_xperm_range_list));
>                 if (sorted == NULL)
>                         goto error;
> -               memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
> +               memcpy(sorted, r, sizeof(struct av_xperm_range_list));
>                 sorted->next = NULL;
>                 if (sortedhead == NULL) {
>                         sortedhead = sorted;
> @@ -1877,9 +1877,9 @@ error:
>         return -1;
>  }
>
> -static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *r, *tmp;
> +       struct av_xperm_range_list *r, *tmp;
>         r = *rangehead;
>         while (r != NULL && r->next != NULL) {
>                 /* merge */
> @@ -1897,15 +1897,15 @@ static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
>         return 0;
>  }
>
> -static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
>  {
>         char *id;
> -       struct av_ioctl_range_list *rnew, *r = NULL;
> +       struct av_xperm_range_list *rnew, *r = NULL;
>         uint8_t omit = 0;
>
>         *rangehead = NULL;
>
> -       /* read in all the ioctl commands */
> +       /* read in all the netlink ranges / ioctl commands */
>         while ((id = queue_remove(id_queue))) {
>                 if (strcmp(id,"~") == 0) {
>                         /* these are values to be omitted */
> @@ -1917,13 +1917,13 @@ static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
>                         id = queue_remove(id_queue);
>                         r->range.high = (uint16_t) strtoul(id,NULL,0);
>                         if (r->range.high < r->range.low) {
> -                               yyerror("Ioctl ranges must be in ascending order.");
> +                               yyerror("xperm ranges must be in ascending order.");
>                                 return -1;
>                         }
>                         free(id);
>                 } else {
>                         /* read in new low value */
> -                       rnew = malloc(sizeof(struct av_ioctl_range_list));
> +                       rnew = malloc(sizeof(struct av_xperm_range_list));
>                         if (rnew == NULL)
>                                 goto error;
>                         rnew->next = NULL;
> @@ -1950,11 +1950,11 @@ error:
>  }
>
>  /* flip to included ranges */
> -static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> +static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
>  {
> -       struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
> +       struct av_xperm_range_list *rnew, *r, *newhead, *r2;
>
> -       rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> +       rnew = calloc(1, sizeof(struct av_xperm_range_list));
>         if (!rnew)
>                 goto error;
>
> @@ -1972,7 +1972,7 @@ static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
>
>         while (r) {
>                 r2->range.high = r->range.low - 1;
> -               rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> +               rnew = calloc(1, sizeof(struct av_xperm_range_list));
>                 if (!rnew)
>                         goto error;
>                 r2->next = rnew;
> @@ -1998,27 +1998,27 @@ error:
>         return -1;
>  }
>
> -static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
> +static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
>  {
> -       struct av_ioctl_range_list *rangehead;
> +       struct av_xperm_range_list *rangehead;
>         uint8_t omit;
>
>         /* read in ranges to include and omit */
> -       if (avrule_read_ioctls(&rangehead))
> +       if (avrule_read_xperm_ranges(&rangehead))
>                 return -1;
>         if (rangehead == NULL) {
> -               yyerror("error processing ioctl commands");
> +               yyerror("error processing ioctl/netlink commands");
>                 return -1;
>         }
>         omit = rangehead->omit;
> -       /* sort and merge the input ioctls */
> -       if (avrule_sort_ioctls(&rangehead))
> +       /* sort and merge the input ranges */
> +       if (avrule_sort_xperms(&rangehead))
>                 return -1;
> -       if (avrule_merge_ioctls(&rangehead))
> +       if (avrule_merge_xperms(&rangehead))
>                 return -1;
>         /* flip ranges if these are omitted */
>         if (omit) {
> -               if (avrule_omit_ioctls(&rangehead))
> +               if (avrule_omit_xperms(&rangehead))
>                         return -1;
>         }
>
> @@ -2189,11 +2189,11 @@ static int avrule_xperms_used(const av_extended_perms_t *xperms)
>  #define IOC_DRIV(x) ((x) >> 8)
>  #define IOC_FUNC(x) ((x) & 0xff)
>  #define IOC_CMD(driver, func) (((driver) << 8) + (func))
> -static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> +static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
>                                 av_extended_perms_t *complete_driver,
>                                 av_extended_perms_t **extended_perms)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint8_t low, high;
>
> @@ -2228,10 +2228,10 @@ static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
>
>  }
>
> -static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> +static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
>                         av_extended_perms_t **extended_perms)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint16_t low, high;
>         xperms = calloc(1, sizeof(av_extended_perms_t));
> @@ -2270,10 +2270,10 @@ static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
>         return 0;
>  }
>
> -static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> +static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
>                 av_extended_perms_t **extended_perms, unsigned int driver)
>  {
> -       struct av_ioctl_range_list *r;
> +       struct av_xperm_range_list *r;
>         av_extended_perms_t *xperms;
>         uint16_t low, high;
>
> @@ -2307,7 +2307,6 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
>                 high = IOC_FUNC(high);
>                 avrule_xperm_setrangebits(low, high, xperms);
>                 xperms->driver = driver;
> -               xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
>                 r = r->next;
>         }
>
> @@ -2320,6 +2319,18 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
>         return 0;
>  }
>
> +void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
> +{
> +       struct av_xperm_range_list *r, *tmp;
> +
> +       r = rangelist;
> +       while (r) {
> +               tmp = r;
> +               r = r->next;
> +               free(tmp);
> +       }
> +}
> +
>  static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
>  {
>         unsigned int i;
> @@ -2384,13 +2395,13 @@ static int avrule_cpy(avrule_t *dest, const avrule_t *src)
>  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
>  {
>         avrule_t *avrule;
> -       struct av_ioctl_range_list *rangelist, *r;
> +       struct av_xperm_range_list *rangelist, *r;
>         av_extended_perms_t *complete_driver, *partial_driver, *xperms;
>         unsigned int i;
>
>
>         /* organize ioctl ranges */
> -       if (avrule_ioctl_ranges(&rangelist))
> +       if (avrule_xperm_ranges(&rangelist))
>                 return -1;
>
>         /* create rule for ioctl driver types that are entirely enabled */
> @@ -2422,10 +2433,11 @@ static int define_te_avtab_ioctl(const avrule_t *avrule_template)
>          */
>         i = 0;
>         while (xperms_for_each_bit(&i, partial_driver)) {
> -               if (avrule_ioctl_func(rangelist, &xperms, i))
> +               if (avrule_xperms_single_driver(rangelist, &xperms, i))
>                         return -1;
>
>                 if (xperms) {
> +                       xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
>                         avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
>                         if (!avrule) {
>                                 yyerror("out of memory");
> @@ -2451,6 +2463,38 @@ done:
>         return 0;
>  }
>
> +int define_te_avtab_netlink(avrule_t *avrule_template)
> +{
> +       avrule_t *avrule;
> +       struct av_xperm_range_list *range_list;
> +       av_extended_perms_t *xperms = NULL;
> +
> +       /* organize message ranges */
> +       if (avrule_xperm_ranges(&range_list))
> +               return -1;
> +
> +       /* Netlink message types comfortably fit into a single driver
> +        * (see RTM_MAX in uapi/linux/rtnetlink.h)
> +        */
> +       avrule_xperms_single_driver(range_list, &xperms, 0);
> +
> +       if (xperms && avrule_xperms_used(xperms)) {
> +               xperms->specified = AVRULE_XPERMS_NLMSG;
> +               avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> +               if (!avrule) {
> +                       yyerror("out of memory");
> +                       return -1;
> +               }
> +               if (avrule_cpy(avrule, avrule_template))
> +                       return -1;
> +               avrule->xperms = xperms;
> +               append_avrule(avrule);
> +       } else {
> +               free(xperms);
> +       }
> +       return 0;
> +}
> +
>  int define_te_avtab_extended_perms(int which)
>  {
>         char *id;
> @@ -2473,8 +2517,10 @@ int define_te_avtab_extended_perms(int which)
>         id = queue_remove(id_queue);
>         if (strcmp(id,"ioctl") == 0) {
>                 rc = define_te_avtab_ioctl(avrule_template);
> +       } else if (strcmp(id, "nlmsg") == 0) {
> +               rc = define_te_avtab_netlink(avrule_template);
>         } else {
> -               yyerror("only ioctl extended permissions are supported");
> +               yyerror("only ioctl / nlmsg extended permissions are supported");
>                 rc = -1;
>         }
>
> diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> index ec2a3e9a..f8652ec5 100644
> --- a/checkpolicy/test/dismod.c
> +++ b/checkpolicy/test/dismod.c
> @@ -296,6 +296,8 @@ static int display_avrule(avrule_t * avrule, policydb_t * policy,
>                         xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
>                 else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
>                         xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
> +               else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
> +                       xperms.specified = AVTAB_XPERMS_NLMSG;
>                 else {
>                         fprintf(fp, "     ERROR: no valid xperms specified\n");
>                         return -1;
> diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> index 4cc7f87f..fbdb005a 100644
> --- a/libsepol/cil/src/cil.c
> +++ b/libsepol/cil/src/cil.c
> @@ -219,6 +219,7 @@ char *CIL_KEY_DONTAUDITX;
>  char *CIL_KEY_NEVERALLOWX;
>  char *CIL_KEY_PERMISSIONX;
>  char *CIL_KEY_IOCTL;
> +char *CIL_KEY_NLMSG;
>  char *CIL_KEY_UNORDERED;
>  char *CIL_KEY_SRC_INFO;
>  char *CIL_KEY_SRC_CIL;
> @@ -388,6 +389,7 @@ static void cil_init_keys(void)
>         CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
>         CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
>         CIL_KEY_IOCTL = cil_strpool_add("ioctl");
> +       CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
>         CIL_KEY_UNORDERED = cil_strpool_add("unordered");
>         CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
>         CIL_KEY_SRC_CIL = cil_strpool_add("cil");
> diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> index d8aa495a..f9938d66 100644
> --- a/libsepol/cil/src/cil_binary.c
> +++ b/libsepol/cil/src/cil_binary.c
> @@ -66,6 +66,7 @@ struct cil_args_binary {
>         int pass;
>         hashtab_t role_trans_table;
>         hashtab_t avrulex_ioctl_table;
> +       hashtab_t avrulex_nlmsg_table;
>         void **type_value_to_cil;
>  };
>
> @@ -1553,7 +1554,7 @@ void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
>  #define IOC_DRIV(x) (x >> 8)
>  #define IOC_FUNC(x) (x & 0xff)
>
> -int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> +int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
>  {
>         ebitmap_node_t *node;
>         unsigned int i;
> @@ -1618,13 +1619,53 @@ int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
>         return SEPOL_OK;
>  }
>
> -int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> +{
> +       ebitmap_node_t *node;
> +       uint16_t i;
> +       uint16_t low;
> +       struct avtab_extended_perms *avtab = NULL;
> +       int start_new_range = 1;
> +
> +       cil_list_init(xperms_list, CIL_NONE);
> +
> +       ebitmap_for_each_positive_bit(xperms, node, i) {
> +               if (start_new_range) {
> +                       low = i;
> +                       start_new_range = 0;
> +               }
> +
> +               // Continue if the current bit isn't the end of the driver range
> +               // or the next bit is set
> +               if (ebitmap_get_bit(xperms, i + 1)) {
> +                       continue;
> +               }
> +
> +               start_new_range = 1;
> +
> +               if (!avtab) {
> +                       avtab = cil_calloc(1, sizeof(*avtab));
> +                       // Netlink message types all fit in driver 0
> +                       avtab->driver = 0;
> +                       avtab->specified = AVTAB_XPERMS_NLMSG;
> +               }
> +
> +               __avrule_xperm_setrangebits(low, i, avtab);
> +       }
> +
> +       if (avtab) {
> +               cil_list_append(*xperms_list, CIL_NONE, avtab);
> +       }
> +
> +       return SEPOL_OK;
> +}
> +
> +int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
>  {
>         int rc = SEPOL_OK;
>         struct policydb *pdb;
>         avtab_key_t *avtab_key;
>         avtab_datum_t avtab_datum;
> -       struct cil_list *xperms_list = NULL;
>         struct cil_list_item *item;
>         class_datum_t *sepol_obj;
>         uint32_t data = 0;
> @@ -1637,17 +1678,12 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
>         // setting the data for an extended avtab isn't really necessary because
>         // it is ignored by the kernel. However, neverallow checking requires that
>         // the data value be set, so set it for that to work.
> -       rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
> +       rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
>         if (rc != SEPOL_OK) {
>                 goto exit;
>         }
>         avtab_datum.data = data;
>
> -       rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> -       if (rc != SEPOL_OK) {
> -               goto exit;
> -       }
> -
>         cil_list_for_each(item, xperms_list) {
>                 avtab_datum.xperms = item->data;
>                 rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
> @@ -1659,16 +1695,57 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
>         rc = SEPOL_OK;
>
>  exit:
> +       return rc;
> +}
> +
> +void __cil_cleanup_xperms(struct cil_list *xperms_list)
> +{
> +       struct cil_list_item *item;
> +
>         if (xperms_list != NULL) {
>                 cil_list_for_each(item, xperms_list) {
>                         free(item->data);
> +                       item->data = NULL;
>                 }
>                 cil_list_destroy(&xperms_list, CIL_FALSE);
>         }
> +}
> +
> +int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +{
> +       struct cil_list *xperms_list = NULL;
> +       int rc;
> +
> +       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> +       if (rc != SEPOL_OK) {
> +               goto exit;
> +       }
> +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
> +
> +exit:
> +       __cil_cleanup_xperms(xperms_list);
> +
>         return rc;
>  }
>
> -int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> +int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> +{
> +       struct cil_list *xperms_list = NULL;
> +       int rc;
> +
> +       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> +       if (rc != SEPOL_OK) {
> +               goto exit;
> +       }
> +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
> +
> +exit:
> +       __cil_cleanup_xperms(xperms_list);
> +
> +       return rc;
> +}
> +
> +int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
>  {
>         uint16_t specified;
>         avtab_key_t *avtab_key;
> @@ -1747,8 +1824,12 @@ int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
>                 if (rc != SEPOL_OK) goto exit;
>
>                 switch (permx->kind) {
> -               case  CIL_PERMX_KIND_IOCTL:
> -                       rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> +               case CIL_PERMX_KIND_IOCTL:
> +                       rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> +                       if (rc != SEPOL_OK) goto exit;
> +                       break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
>                         if (rc != SEPOL_OK) goto exit;
>                         break;
>                 default:
> @@ -4417,6 +4498,9 @@ static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
>                         case CIL_PERMX_KIND_IOCTL:
>                                 perm_str = CIL_KEY_IOCTL;
>                                 break;
> +                       case CIL_PERMX_KIND_NLMSG:
> +                               perm_str = CIL_KEY_NLMSG;
> +                               break;
>                         default:
>                                 rc = SEPOL_ERR;
>                                 goto exit;
> @@ -4563,6 +4647,9 @@ static void __cil_print_permissionx(struct cil_permissionx *px)
>                 case CIL_PERMX_KIND_IOCTL:
>                         kind_str = CIL_KEY_IOCTL;
>                         break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       kind_str = CIL_KEY_NLMSG;
> +                       break;
>                 default:
>                         kind_str = "unknown";
>                         break;
> @@ -4696,8 +4783,21 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
>                         goto exit;
>                 }
>
> -               rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> -               if (rc != SEPOL_OK) {
> +               switch (cil_rule->perms.x.permx->kind) {
> +               case CIL_PERMX_KIND_IOCTL:
> +                       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> +                       if (rc != SEPOL_OK) {
> +                               goto exit;
> +                       }
> +                       break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> +                       if (rc != SEPOL_OK) {
> +                               goto exit;
> +                       }
> +                       break;
> +               default:
> +                       rc = SEPOL_ERR;
>                         goto exit;
>                 }
>
> @@ -4715,13 +4815,7 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
>         }
>
>  exit:
> -       if (xperms != NULL) {
> -               cil_list_for_each(item, xperms) {
> -                       free(item->data);
> -                       item->data = NULL;
> -               }
> -               cil_list_destroy(&xperms, CIL_FALSE);
> -       }
> +       __cil_cleanup_xperms(xperms);
>
>         rule->xperms = NULL;
>         __cil_destroy_sepol_avrules(rule);
> @@ -4904,6 +4998,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>         struct cil_list *neverallows = NULL;
>         hashtab_t role_trans_table = NULL;
>         hashtab_t avrulex_ioctl_table = NULL;
> +       hashtab_t avrulex_nlmsg_table = NULL;
>         void **type_value_to_cil = NULL;
>         struct cil_class **class_value_to_cil = NULL;
>         struct cil_perm ***perm_value_to_cil = NULL;
> @@ -4947,7 +5042,13 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>
>         avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
>         if (!avrulex_ioctl_table) {
> -               cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
> +               cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
> +               goto exit;
> +       }
> +
> +       avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> +       if (!avrulex_nlmsg_table) {
> +               cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
>                 goto exit;
>         }
>
> @@ -4958,6 +5059,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>         extra_args.neverallows = neverallows;
>         extra_args.role_trans_table = role_trans_table;
>         extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
> +       extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
>         extra_args.type_value_to_cil = type_value_to_cil;
>
>         for (i = 1; i <= 3; i++) {
> @@ -4980,7 +5082,12 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
>                 if (i == 3) {
>                         rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
>                         if (rc != SEPOL_OK) {
> -                               cil_log(CIL_INFO, "Failure creating avrulex rules\n");
> +                               cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
> +                               goto exit;
> +                       }
> +                       rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
> +                       if (rc != SEPOL_OK) {
> +                               cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
>                                 goto exit;
>                         }
>                 }
> @@ -5056,6 +5163,7 @@ exit:
>         hashtab_destroy(role_trans_table);
>         hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
>         hashtab_destroy(avrulex_ioctl_table);
> +       hashtab_destroy(avrulex_nlmsg_table);
>         free(type_value_to_cil);
>         free(class_value_to_cil);
>         if (perm_value_to_cil != NULL) {
> diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> index 9c34be23..b1cfb4f0 100644
> --- a/libsepol/cil/src/cil_build_ast.c
> +++ b/libsepol/cil/src/cil_build_ast.c
> @@ -2159,8 +2159,10 @@ int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
>
>         if (parse_current->data == CIL_KEY_IOCTL) {
>                 permx->kind = CIL_PERMX_KIND_IOCTL;
> +       } else if (parse_current->data == CIL_KEY_NLMSG) {
> +               permx->kind = CIL_PERMX_KIND_NLMSG;
>         } else {
> -               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
> +               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
>                 rc = SEPOL_ERR;
>                 goto exit;
>         }
> diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> index 6f1d3cb5..c968cf1e 100644
> --- a/libsepol/cil/src/cil_internal.h
> +++ b/libsepol/cil/src/cil_internal.h
> @@ -236,6 +236,7 @@ extern char *CIL_KEY_DONTAUDITX;
>  extern char *CIL_KEY_NEVERALLOWX;
>  extern char *CIL_KEY_PERMISSIONX;
>  extern char *CIL_KEY_IOCTL;
> +extern char *CIL_KEY_NLMSG;
>  extern char *CIL_KEY_UNORDERED;
>  extern char *CIL_KEY_SRC_INFO;
>  extern char *CIL_KEY_SRC_CIL;
> @@ -623,6 +624,7 @@ struct cil_avrule {
>  };
>
>  #define CIL_PERMX_KIND_IOCTL 1
> +#define CIL_PERMX_KIND_NLMSG 2
>  struct cil_permissionx {
>         struct cil_symtab_datum datum;
>         uint32_t kind;
> diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> index 7c543c47..2703d764 100644
> --- a/libsepol/cil/src/cil_policy.c
> +++ b/libsepol/cil/src/cil_policy.c
> @@ -1112,6 +1112,8 @@ static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
>
>         if (permx->kind == CIL_PERMX_KIND_IOCTL) {
>                 kind = "ioctl";
> +       } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +               kind = CIL_KEY_NLMSG;
>         } else {
>                 kind = "???";
>         }
> diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
> index d994d717..67bdb0e9 100644
> --- a/libsepol/cil/src/cil_verify.c
> +++ b/libsepol/cil/src/cil_verify.c
> @@ -1427,6 +1427,9 @@ int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
>                 case CIL_PERMX_KIND_IOCTL:
>                         kind_str = CIL_KEY_IOCTL;
>                         break;
> +               case CIL_PERMX_KIND_NLMSG:
> +                       kind_str = CIL_KEY_NLMSG;
> +                       break;
>                 default:
>                         cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
>                         rc = SEPOL_ERR;
> diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> index d7f00bcc..b93a4fa1 100644
> --- a/libsepol/cil/src/cil_write_ast.c
> +++ b/libsepol/cil/src/cil_write_ast.c
> @@ -303,7 +303,13 @@ static void write_permx(FILE *out, struct cil_permissionx *permx)
>                 fprintf(out, "%s", datum_to_str(DATUM(permx)));
>         } else {
>                 fprintf(out, "(");
> -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> +                       fprintf(out, "%s ", "ioctl");
> +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +                       fprintf(out, "%s ", "nlmsg");
> +               } else {
> +                       fprintf(out, "%s ", "<?KIND>");
> +               }
>                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
>                 write_expr(out, permx->expr_str);
>                 fprintf(out, ")");
> @@ -812,7 +818,13 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
>         case CIL_PERMISSIONX: {
>                 struct cil_permissionx *permx = node->data;
>                 fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
> -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> +                       fprintf(out, "%s ", "ioctl");
> +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> +                       fprintf(out, "%s ", "nlmsg");
> +               } else {
> +                       fprintf(out, "%s ", "<?KIND>");
> +               }
>                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
>                 write_expr(out, permx->expr_str);
>                 fprintf(out, "))\n");
> diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
> index 10ecde9a..aa7481d3 100644
> --- a/libsepol/include/sepol/policydb/avtab.h
> +++ b/libsepol/include/sepol/policydb/avtab.h
> @@ -74,6 +74,7 @@ typedef struct avtab_extended_perms {
>
>  #define AVTAB_XPERMS_IOCTLFUNCTION     0x01
>  #define AVTAB_XPERMS_IOCTLDRIVER       0x02
> +#define AVTAB_XPERMS_NLMSG             0x03
>         /* extension of the avtab_key specified */
>         uint8_t specified;
>         uint8_t driver;
> diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> index 4bf9f05d..55918e1d 100644
> --- a/libsepol/include/sepol/policydb/policydb.h
> +++ b/libsepol/include/sepol/policydb/policydb.h
> @@ -259,6 +259,7 @@ typedef struct class_perm_node {
>  typedef struct av_extended_perms {
>  #define AVRULE_XPERMS_IOCTLFUNCTION    0x01
>  #define AVRULE_XPERMS_IOCTLDRIVER      0x02
> +#define AVRULE_XPERMS_NLMSG            0x03
>         uint8_t specified;
>         uint8_t driver;
>         /* 256 bits of permissions */
> diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
> index dd2749a0..47e45713 100644
> --- a/libsepol/src/assertion.c
> +++ b/libsepol/src/assertion.c
> @@ -101,6 +101,9 @@ static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
>         } else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
>                         && (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
>                 rc = extended_permissions_and(neverallow->perms, allow->perms);
> +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> +               rc = extended_permissions_and(neverallow->perms, allow->perms);
>         }
>
>         return rc;
> @@ -133,6 +136,12 @@ static void extended_permissions_violated(avtab_extended_perms_t *result,
>                 result->specified = AVTAB_XPERMS_IOCTLDRIVER;
>                 for (i = 0; i < EXTENDED_PERMS_LEN; i++)
>                         result->perms[i] = neverallow->perms[i] & allow->perms[i];
> +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> +               result->specified = AVTAB_XPERMS_NLMSG;
> +               result->driver = 0;
> +               for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> +                       result->perms[i] = neverallow->perms[i] & allow->perms[i];
>         }
>  }
>
> @@ -166,7 +175,8 @@ static int report_assertion_extended_permissions(sepol_handle_t *handle,
>                              node = avtab_search_node_next(node, tmp_key.specified)) {
>                                 xperms = node->datum.xperms;
>                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                                         continue;
>
>                                 rc = check_extended_permissions(avrule->xperms, xperms);
> @@ -346,7 +356,8 @@ static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
>                                 xperms = node->datum.xperms;
>
>                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                                         continue;
>                                 rc = check_extended_permissions(neverallow_xperms, xperms);
>                                 if (rc)
> diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> index a6a466f7..959e6eb2 100644
> --- a/libsepol/src/expand.c
> +++ b/libsepol/src/expand.c
> @@ -1796,6 +1796,9 @@ static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
>         case AVRULE_XPERMS_IOCTLDRIVER:
>                 xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
>                 break;
> +       case AVRULE_XPERMS_NLMSG:
> +               xperms->specified = AVTAB_XPERMS_NLMSG;
> +               break;
>         default:
>                 return -1;
>         }
> diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> index 305567a5..090d4e43 100644
> --- a/libsepol/src/kernel_to_cil.c
> +++ b/libsepol/src/kernel_to_cil.c
> @@ -1617,7 +1617,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
>         remaining = sizeof(xpermsbuf);
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG)) {
>                 return NULL;
>         }
>
> @@ -1637,7 +1638,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
>                         continue;
>                 }
>
> -               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
> +               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
> +                       || xperms->specified & AVTAB_XPERMS_NLMSG) {
>                         value = xperms->driver<<8 | bit;
>                         if (in_range) {
>                                 low_value = xperms->driver<<8 | low_bit;
> @@ -1679,7 +1681,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
>  {
>         uint32_t data = datum->data;
>         type_datum_t *type;
> -       const char *flavor, *tgt;
> +       const char *flavor, *tgt, *func;
>         char *src, *class, *perms, *new;
>         char *rule = NULL;
>
> @@ -1742,8 +1744,21 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
>                         goto exit;
>                 }
>
> +               switch (datum->xperms->specified) {
> +               case AVRULE_XPERMS_IOCTLDRIVER:
> +               case AVRULE_XPERMS_IOCTLFUNCTION:
> +                       func = "ioctl";
> +                       break;
> +               case AVRULE_XPERMS_NLMSG:
> +                       func = "nlmsg";
> +                       break;
> +               default:
> +                       sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
> +                       goto exit;
> +               }
> +
>                 rule = create_str("(%s %s %s (%s %s (%s)))", 6,
> -                                 flavor, src, tgt, "ioctl", class, perms);
> +                                 flavor, src, tgt, func, class, perms);
>         } else {
>                 new = pdb->p_type_val_to_name[data - 1];
>
> diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> index 16e4004e..47d9b171 100644
> --- a/libsepol/src/module_to_cil.c
> +++ b/libsepol/src/module_to_cil.c
> @@ -627,14 +627,15 @@ exit:
>  static int xperms_to_cil(const av_extended_perms_t *xperms)
>  {
>         uint16_t value;
> -       uint16_t low_bit;
> +       uint16_t low_bit = 0;
>         uint16_t low_value;
>         unsigned int bit;
>         unsigned int in_range = 0;
>         int first = 1;
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                 return -1;
>
>         for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
> @@ -674,6 +675,13 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
>                         } else {
>                                 cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
>                         }
> +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> +                       if (in_range) {
> +                               cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
> +                               in_range = 0;
> +                       } else {
> +                               cil_printf("0x%hx", (uint16_t) bit);
> +                       }
>                 }
>         }
>
> @@ -683,7 +691,7 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
>  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
>  {
>         int rc = -1;
> -       const char *rule;
> +       const char *rule, *func;
>         const struct class_perm_node *classperm;
>
>         switch (type) {
> @@ -705,10 +713,23 @@ static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
>                 goto exit;
>         }
>
> +       switch (xperms->specified) {
> +       case AVRULE_XPERMS_IOCTLDRIVER:
> +       case AVRULE_XPERMS_IOCTLFUNCTION:
> +               func = "ioctl";
> +               break;
> +       case AVRULE_XPERMS_NLMSG:
> +               func = "nlmsg";
> +               break;
> +       default:
> +               log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
> +               goto exit;
> +       }
> +
>         for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
>                 cil_indent(indent);
>                 cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
> -                          "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
> +                          func, pdb->p_class_val_to_name[classperm->tclass - 1]);
>                 xperms_to_cil(xperms);
>                 cil_printf(")))\n");
>         }
> diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
> index 6826155c..f23cfd17 100644
> --- a/libsepol/src/optimize.c
> +++ b/libsepol/src/optimize.c
> @@ -180,6 +180,12 @@ static int process_avtab_datum(uint16_t specified,
>
>                         if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
>                                 return process_xperms(x1->perms, x2->perms);
> +               } else if (x1->specified == AVTAB_XPERMS_NLMSG) {
> +                       if (x2->specified == AVTAB_XPERMS_NLMSG) {
> +                               if (x1->driver != x2->driver)
> +                                       return 0;
> +                               return process_xperms(x1->perms, x2->perms);
> +                       }
>                 }
>                 return 0;
>         }
> diff --git a/libsepol/src/util.c b/libsepol/src/util.c
> index 902c63c5..b5b41f87 100644
> --- a/libsepol/src/util.c
> +++ b/libsepol/src/util.c
> @@ -124,7 +124,7 @@ char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
>  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>  {
>         uint16_t value;
> -       uint16_t low_bit;
> +       uint16_t low_bit = 0;
>         uint16_t low_value;
>         unsigned int bit;
>         unsigned int in_range = 0;
> @@ -135,7 +135,8 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>         p = xpermsbuf;
>
>         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
>                 return NULL;
>
>         len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");

Obviously, it won't always be "ioctl" now.


> @@ -173,6 +174,12 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
>                                 len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
>                         }
>
> +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> +                       if (in_range) {
> +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
> +                       } else {
> +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
> +                       }
>                 }
>
>                 if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))
> --
> 2.34.0.rc0.344.g81b53c2807-goog
>

Something is not working correctly.
I should be able to compile both a CIL and policy.conf and then
convert them back to CIL and policy.conf. The CIL policy has nothing
for the xperms, while the policy.conf doesn't even have the rules.

So in the original CIL policy we have
(allowx TYPE_X2 TYPE_OBJX2 (ioctl CLASS2 (0x1110)))
(allowx TYPE_X3 TYPE_OBJX3 (nlmsg CLASS3 (0x1110)))
The resulting policy, when it is converted back to CIL, is
(allowx TYPE_X2 TYPE_OBJX2 (ioctl CLASS2 ((0x1110))))
(allowx TYPE_X3 TYPE_OBJX3 (nlmsg CLASS3 ())))

Using secil2conf generates the correct policy.conf from CIL, so I
suspect something is wrong in reading or writing the binary policy.

For the policy.conf, we have
allowxperm TYPE_X2 TYPE_OBJX2:CLASS2 ioctl { 0x1110 };
allowxperm TYPE_X3 TYPE_OBJX3:CLASS3 nlmsg { 0x1110 };
The resulting policy, when converted back to a policy.conf is
allowxperm TYPE_X2 TYPE_OBJX2:CLASS2 ioctl { 0x1110 };

Using "checkpolicy -M -C -o test.conf.cil test.conf" results in the
nlmsg rules being left out as well.

I am investigating and I will let you know what I find.

Jim
James Carter Nov. 16, 2021, 9:33 p.m. UTC | #5
On Tue, Nov 16, 2021 at 10:44 AM James Carter <jwcart2@gmail.com> wrote:
>
> On Wed, Nov 10, 2021 at 6:12 AM Bram Bonne <brambonne@google.com> wrote:
> >
> > Reuse the existing extended permissions infrastructure to support
> > sepolicy for different netlink message types.
> >
> > When individual netlink message types are omitted only the existing
> > permissions are checked. As is the case for ioctl xperms, this feature
> > is intended to provide finer granularity for nlmsg_read and nlmsg_write
> > permissions, as they may be too imprecise. For example, a single
> > NETLINK_ROUTE socket may provide access to both an interface's IP
> > address and to its ARP table, which might have different privacy
> > implications. In addition, the set of message types has grown over time,
> > so even if the current list is acceptable, future additions might not be.
> > It was suggested before on the mailing list [1] that extended permissions
> > would be a good fit for this purpose.
> >
> > Existing policy using the nlmsg_read and nlmsg_write permissions will
> > continue to work as-is. Similar to ioctl xperms, netlink xperms allow
> > for a more fine-grained policy where needed.
> >
> > Example policy on Android, preventing regular apps from accessing the
> > device's MAC address and ARP table, but allowing this access to
> > privileged apps, looks as follows:
> >
> > allow netdomain self:netlink_route_socket {
> >         create read getattr write setattr lock append connect getopt
> >         setopt shutdown nlmsg_read
> > };
> > allowxperm netdomain self:netlink_route_socket nlmsg ~{
> >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> > };
> > allowxperm priv_app self:netlink_route_socket nlmsg {
> >         RTM_GETLINK RTM_GETNEIGH RTM_GETNEIGHTBL
> > };
> >
> > Android currently uses code similar to [1] as a temporary workaround to
> > limit access to certain netlink message types; our hope is that this patch
> > will allow us to move back to upstream code with an approach that works for
> > everyone.
> >
> > [1] https://lore.kernel.org/selinux/CAHC9VhRSUhozBycHMZcMaJsibJDxNMsTsKVT2zOnW=5H4R4mdg@mail.gmail.com/
> >
> > Signed-off-by: Bram Bonne <brambonne@google.com>
> > ---
> >  checkpolicy/policy_define.c                | 124 +++++++++++------
> >  checkpolicy/test/dismod.c                  |   2 +
> >  libsepol/cil/src/cil.c                     |   2 +
> >  libsepol/cil/src/cil_binary.c              | 154 ++++++++++++++++++---
> >  libsepol/cil/src/cil_build_ast.c           |   4 +-
> >  libsepol/cil/src/cil_internal.h            |   2 +
> >  libsepol/cil/src/cil_policy.c              |   2 +
> >  libsepol/cil/src/cil_verify.c              |   3 +
> >  libsepol/cil/src/cil_write_ast.c           |  16 ++-
> >  libsepol/include/sepol/policydb/avtab.h    |   1 +
> >  libsepol/include/sepol/policydb/policydb.h |   1 +
> >  libsepol/src/assertion.c                   |  15 +-
> >  libsepol/src/expand.c                      |   3 +
> >  libsepol/src/kernel_to_cil.c               |  23 ++-
> >  libsepol/src/module_to_cil.c               |  29 +++-
> >  libsepol/src/optimize.c                    |   6 +
> >  libsepol/src/util.c                        |  11 +-
> >  17 files changed, 321 insertions(+), 77 deletions(-)
> >
> > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > index d3eb6111..8ca22993 100644
> > --- a/checkpolicy/policy_define.c
> > +++ b/checkpolicy/policy_define.c
> > @@ -1818,27 +1818,27 @@ avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
> >         return sl;
> >  }
> >
> > -typedef struct av_ioctl_range {
> > +typedef struct av_xperm_range {
> >         uint16_t low;
> >         uint16_t high;
> > -} av_ioctl_range_t;
> > +} av_xperm_range_t;
> >
> > -struct av_ioctl_range_list {
> > +struct av_xperm_range_list {
> >         uint8_t omit;
> > -       av_ioctl_range_t range;
> > -       struct av_ioctl_range_list *next;
> > +       av_xperm_range_t range;
> > +       struct av_xperm_range_list *next;
> >  };
> >
> > -static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
> > +       struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
> >
> >         /* order list by range.low */
> >         for (r = *rangehead; r != NULL; r = r->next) {
> > -               sorted = malloc(sizeof(struct av_ioctl_range_list));
> > +               sorted = malloc(sizeof(struct av_xperm_range_list));
> >                 if (sorted == NULL)
> >                         goto error;
> > -               memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
> > +               memcpy(sorted, r, sizeof(struct av_xperm_range_list));
> >                 sorted->next = NULL;
> >                 if (sortedhead == NULL) {
> >                         sortedhead = sorted;
> > @@ -1877,9 +1877,9 @@ error:
> >         return -1;
> >  }
> >
> > -static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *r, *tmp;
> > +       struct av_xperm_range_list *r, *tmp;
> >         r = *rangehead;
> >         while (r != NULL && r->next != NULL) {
> >                 /* merge */
> > @@ -1897,15 +1897,15 @@ static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
> >         return 0;
> >  }
> >
> > -static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
> >  {
> >         char *id;
> > -       struct av_ioctl_range_list *rnew, *r = NULL;
> > +       struct av_xperm_range_list *rnew, *r = NULL;
> >         uint8_t omit = 0;
> >
> >         *rangehead = NULL;
> >
> > -       /* read in all the ioctl commands */
> > +       /* read in all the netlink ranges / ioctl commands */
> >         while ((id = queue_remove(id_queue))) {
> >                 if (strcmp(id,"~") == 0) {
> >                         /* these are values to be omitted */
> > @@ -1917,13 +1917,13 @@ static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
> >                         id = queue_remove(id_queue);
> >                         r->range.high = (uint16_t) strtoul(id,NULL,0);
> >                         if (r->range.high < r->range.low) {
> > -                               yyerror("Ioctl ranges must be in ascending order.");
> > +                               yyerror("xperm ranges must be in ascending order.");
> >                                 return -1;
> >                         }
> >                         free(id);
> >                 } else {
> >                         /* read in new low value */
> > -                       rnew = malloc(sizeof(struct av_ioctl_range_list));
> > +                       rnew = malloc(sizeof(struct av_xperm_range_list));
> >                         if (rnew == NULL)
> >                                 goto error;
> >                         rnew->next = NULL;
> > @@ -1950,11 +1950,11 @@ error:
> >  }
> >
> >  /* flip to included ranges */
> > -static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> > +static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
> >  {
> > -       struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
> > +       struct av_xperm_range_list *rnew, *r, *newhead, *r2;
> >
> > -       rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > +       rnew = calloc(1, sizeof(struct av_xperm_range_list));
> >         if (!rnew)
> >                 goto error;
> >
> > @@ -1972,7 +1972,7 @@ static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
> >
> >         while (r) {
> >                 r2->range.high = r->range.low - 1;
> > -               rnew = calloc(1, sizeof(struct av_ioctl_range_list));
> > +               rnew = calloc(1, sizeof(struct av_xperm_range_list));
> >                 if (!rnew)
> >                         goto error;
> >                 r2->next = rnew;
> > @@ -1998,27 +1998,27 @@ error:
> >         return -1;
> >  }
> >
> > -static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
> > +static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
> >  {
> > -       struct av_ioctl_range_list *rangehead;
> > +       struct av_xperm_range_list *rangehead;
> >         uint8_t omit;
> >
> >         /* read in ranges to include and omit */
> > -       if (avrule_read_ioctls(&rangehead))
> > +       if (avrule_read_xperm_ranges(&rangehead))
> >                 return -1;
> >         if (rangehead == NULL) {
> > -               yyerror("error processing ioctl commands");
> > +               yyerror("error processing ioctl/netlink commands");
> >                 return -1;
> >         }
> >         omit = rangehead->omit;
> > -       /* sort and merge the input ioctls */
> > -       if (avrule_sort_ioctls(&rangehead))
> > +       /* sort and merge the input ranges */
> > +       if (avrule_sort_xperms(&rangehead))
> >                 return -1;
> > -       if (avrule_merge_ioctls(&rangehead))
> > +       if (avrule_merge_xperms(&rangehead))
> >                 return -1;
> >         /* flip ranges if these are omitted */
> >         if (omit) {
> > -               if (avrule_omit_ioctls(&rangehead))
> > +               if (avrule_omit_xperms(&rangehead))
> >                         return -1;
> >         }
> >
> > @@ -2189,11 +2189,11 @@ static int avrule_xperms_used(const av_extended_perms_t *xperms)
> >  #define IOC_DRIV(x) ((x) >> 8)
> >  #define IOC_FUNC(x) ((x) & 0xff)
> >  #define IOC_CMD(driver, func) (((driver) << 8) + (func))
> > -static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> > +static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
> >                                 av_extended_perms_t *complete_driver,
> >                                 av_extended_perms_t **extended_perms)
> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint8_t low, high;
> >
> > @@ -2228,10 +2228,10 @@ static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
> >
> >  }
> >
> > -static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> > +static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
> >                         av_extended_perms_t **extended_perms)
> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint16_t low, high;
> >         xperms = calloc(1, sizeof(av_extended_perms_t));
> > @@ -2270,10 +2270,10 @@ static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
> >         return 0;
> >  }
> >
> > -static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> > +static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
> >                 av_extended_perms_t **extended_perms, unsigned int driver)

I thought netlink message types were 16-bit, but they are limited to 8
bits in checkpolicy because the driver must be 0.
In avrule_xperms_single_driver() the following check is made
  if ((driver != IOC_DRIV(low)) && (driver != IOC_DRIV(high)))
with 0 passed in for the driver. This causes any nlmsg rule for a
message type  > 255 to be skipped.

I am not sure what was happening with the CIL policy, but limiting the
netlink message types to be < 256 fixes the problem.

There needs to be error messages if the netlink message type is > 255.

Jim


> >  {
> > -       struct av_ioctl_range_list *r;
> > +       struct av_xperm_range_list *r;
> >         av_extended_perms_t *xperms;
> >         uint16_t low, high;
> >
> > @@ -2307,7 +2307,6 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> >                 high = IOC_FUNC(high);
> >                 avrule_xperm_setrangebits(low, high, xperms);
> >                 xperms->driver = driver;
> > -               xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> >                 r = r->next;
> >         }
> >
> > @@ -2320,6 +2319,18 @@ static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
> >         return 0;
> >  }
> >
> > +void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
> > +{
> > +       struct av_xperm_range_list *r, *tmp;
> > +
> > +       r = rangelist;
> > +       while (r) {
> > +               tmp = r;
> > +               r = r->next;
> > +               free(tmp);
> > +       }
> > +}
> > +
> >  static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
> >  {
> >         unsigned int i;
> > @@ -2384,13 +2395,13 @@ static int avrule_cpy(avrule_t *dest, const avrule_t *src)
> >  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> >  {
> >         avrule_t *avrule;
> > -       struct av_ioctl_range_list *rangelist, *r;
> > +       struct av_xperm_range_list *rangelist, *r;
> >         av_extended_perms_t *complete_driver, *partial_driver, *xperms;
> >         unsigned int i;
> >
> >
> >         /* organize ioctl ranges */
> > -       if (avrule_ioctl_ranges(&rangelist))
> > +       if (avrule_xperm_ranges(&rangelist))
> >                 return -1;
> >
> >         /* create rule for ioctl driver types that are entirely enabled */
> > @@ -2422,10 +2433,11 @@ static int define_te_avtab_ioctl(const avrule_t *avrule_template)
> >          */
> >         i = 0;
> >         while (xperms_for_each_bit(&i, partial_driver)) {
> > -               if (avrule_ioctl_func(rangelist, &xperms, i))
> > +               if (avrule_xperms_single_driver(rangelist, &xperms, i))
> >                         return -1;
> >
> >                 if (xperms) {
> > +                       xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
> >                         avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> >                         if (!avrule) {
> >                                 yyerror("out of memory");
> > @@ -2451,6 +2463,38 @@ done:
> >         return 0;
> >  }
> >
> > +int define_te_avtab_netlink(avrule_t *avrule_template)
> > +{
> > +       avrule_t *avrule;
> > +       struct av_xperm_range_list *range_list;
> > +       av_extended_perms_t *xperms = NULL;
> > +
> > +       /* organize message ranges */
> > +       if (avrule_xperm_ranges(&range_list))
> > +               return -1;
> > +
> > +       /* Netlink message types comfortably fit into a single driver
> > +        * (see RTM_MAX in uapi/linux/rtnetlink.h)
> > +        */
> > +       avrule_xperms_single_driver(range_list, &xperms, 0);
> > +
> > +       if (xperms && avrule_xperms_used(xperms)) {
> > +               xperms->specified = AVRULE_XPERMS_NLMSG;
> > +               avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
> > +               if (!avrule) {
> > +                       yyerror("out of memory");
> > +                       return -1;
> > +               }
> > +               if (avrule_cpy(avrule, avrule_template))
> > +                       return -1;
> > +               avrule->xperms = xperms;
> > +               append_avrule(avrule);
> > +       } else {
> > +               free(xperms);
> > +       }
> > +       return 0;
> > +}
> > +
> >  int define_te_avtab_extended_perms(int which)
> >  {
> >         char *id;
> > @@ -2473,8 +2517,10 @@ int define_te_avtab_extended_perms(int which)
> >         id = queue_remove(id_queue);
> >         if (strcmp(id,"ioctl") == 0) {
> >                 rc = define_te_avtab_ioctl(avrule_template);
> > +       } else if (strcmp(id, "nlmsg") == 0) {
> > +               rc = define_te_avtab_netlink(avrule_template);
> >         } else {
> > -               yyerror("only ioctl extended permissions are supported");
> > +               yyerror("only ioctl / nlmsg extended permissions are supported");
> >                 rc = -1;
> >         }
> >
> > diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> > index ec2a3e9a..f8652ec5 100644
> > --- a/checkpolicy/test/dismod.c
> > +++ b/checkpolicy/test/dismod.c
> > @@ -296,6 +296,8 @@ static int display_avrule(avrule_t * avrule, policydb_t * policy,
> >                         xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
> >                 else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
> >                         xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
> > +               else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
> > +                       xperms.specified = AVTAB_XPERMS_NLMSG;
> >                 else {
> >                         fprintf(fp, "     ERROR: no valid xperms specified\n");
> >                         return -1;
> > diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> > index 4cc7f87f..fbdb005a 100644
> > --- a/libsepol/cil/src/cil.c
> > +++ b/libsepol/cil/src/cil.c
> > @@ -219,6 +219,7 @@ char *CIL_KEY_DONTAUDITX;
> >  char *CIL_KEY_NEVERALLOWX;
> >  char *CIL_KEY_PERMISSIONX;
> >  char *CIL_KEY_IOCTL;
> > +char *CIL_KEY_NLMSG;
> >  char *CIL_KEY_UNORDERED;
> >  char *CIL_KEY_SRC_INFO;
> >  char *CIL_KEY_SRC_CIL;
> > @@ -388,6 +389,7 @@ static void cil_init_keys(void)
> >         CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
> >         CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
> >         CIL_KEY_IOCTL = cil_strpool_add("ioctl");
> > +       CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
> >         CIL_KEY_UNORDERED = cil_strpool_add("unordered");
> >         CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
> >         CIL_KEY_SRC_CIL = cil_strpool_add("cil");
> > diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> > index d8aa495a..f9938d66 100644
> > --- a/libsepol/cil/src/cil_binary.c
> > +++ b/libsepol/cil/src/cil_binary.c
> > @@ -66,6 +66,7 @@ struct cil_args_binary {
> >         int pass;
> >         hashtab_t role_trans_table;
> >         hashtab_t avrulex_ioctl_table;
> > +       hashtab_t avrulex_nlmsg_table;
> >         void **type_value_to_cil;
> >  };
> >
> > @@ -1553,7 +1554,7 @@ void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
> >  #define IOC_DRIV(x) (x >> 8)
> >  #define IOC_FUNC(x) (x & 0xff)
> >
> > -int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > +int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> >  {
> >         ebitmap_node_t *node;
> >         unsigned int i;
> > @@ -1618,13 +1619,53 @@ int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
> >         return SEPOL_OK;
> >  }
> >
> > -int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
> > +{
> > +       ebitmap_node_t *node;
> > +       uint16_t i;
> > +       uint16_t low;
> > +       struct avtab_extended_perms *avtab = NULL;
> > +       int start_new_range = 1;
> > +
> > +       cil_list_init(xperms_list, CIL_NONE);
> > +
> > +       ebitmap_for_each_positive_bit(xperms, node, i) {
> > +               if (start_new_range) {
> > +                       low = i;
> > +                       start_new_range = 0;
> > +               }
> > +
> > +               // Continue if the current bit isn't the end of the driver range
> > +               // or the next bit is set
> > +               if (ebitmap_get_bit(xperms, i + 1)) {
> > +                       continue;
> > +               }
> > +
> > +               start_new_range = 1;
> > +
> > +               if (!avtab) {
> > +                       avtab = cil_calloc(1, sizeof(*avtab));
> > +                       // Netlink message types all fit in driver 0
> > +                       avtab->driver = 0;
> > +                       avtab->specified = AVTAB_XPERMS_NLMSG;
> > +               }
> > +
> > +               __avrule_xperm_setrangebits(low, i, avtab);
> > +       }
> > +
> > +       if (avtab) {
> > +               cil_list_append(*xperms_list, CIL_NONE, avtab);
> > +       }
> > +
> > +       return SEPOL_OK;
> > +}
> > +
> > +int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
> >  {
> >         int rc = SEPOL_OK;
> >         struct policydb *pdb;
> >         avtab_key_t *avtab_key;
> >         avtab_datum_t avtab_datum;
> > -       struct cil_list *xperms_list = NULL;
> >         struct cil_list_item *item;
> >         class_datum_t *sepol_obj;
> >         uint32_t data = 0;
> > @@ -1637,17 +1678,12 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> >         // setting the data for an extended avtab isn't really necessary because
> >         // it is ignored by the kernel. However, neverallow checking requires that
> >         // the data value be set, so set it for that to work.
> > -       rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
> > +       rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
> >         if (rc != SEPOL_OK) {
> >                 goto exit;
> >         }
> >         avtab_datum.data = data;
> >
> > -       rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > -       if (rc != SEPOL_OK) {
> > -               goto exit;
> > -       }
> > -
> >         cil_list_for_each(item, xperms_list) {
> >                 avtab_datum.xperms = item->data;
> >                 rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
> > @@ -1659,16 +1695,57 @@ int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
> >         rc = SEPOL_OK;
> >
> >  exit:
> > +       return rc;
> > +}
> > +
> > +void __cil_cleanup_xperms(struct cil_list *xperms_list)
> > +{
> > +       struct cil_list_item *item;
> > +
> >         if (xperms_list != NULL) {
> >                 cil_list_for_each(item, xperms_list) {
> >                         free(item->data);
> > +                       item->data = NULL;
> >                 }
> >                 cil_list_destroy(&xperms_list, CIL_FALSE);
> >         }
> > +}
> > +
> > +int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +{
> > +       struct cil_list *xperms_list = NULL;
> > +       int rc;
> > +
> > +       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > +       if (rc != SEPOL_OK) {
> > +               goto exit;
> > +       }
> > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
> > +
> > +exit:
> > +       __cil_cleanup_xperms(xperms_list);
> > +
> >         return rc;
> >  }
> >
> > -int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> > +int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
> > +{
> > +       struct cil_list *xperms_list = NULL;
> > +       int rc;
> > +
> > +       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
> > +       if (rc != SEPOL_OK) {
> > +               goto exit;
> > +       }
> > +       rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
> > +
> > +exit:
> > +       __cil_cleanup_xperms(xperms_list);
> > +
> > +       return rc;
> > +}
> > +
> > +int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
> >  {
> >         uint16_t specified;
> >         avtab_key_t *avtab_key;
> > @@ -1747,8 +1824,12 @@ int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
> >                 if (rc != SEPOL_OK) goto exit;
> >
> >                 switch (permx->kind) {
> > -               case  CIL_PERMX_KIND_IOCTL:
> > -                       rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > +               case CIL_PERMX_KIND_IOCTL:
> > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> > +                       if (rc != SEPOL_OK) goto exit;
> > +                       break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
> >                         if (rc != SEPOL_OK) goto exit;
> >                         break;
> >                 default:
> > @@ -4417,6 +4498,9 @@ static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
> >                         case CIL_PERMX_KIND_IOCTL:
> >                                 perm_str = CIL_KEY_IOCTL;
> >                                 break;
> > +                       case CIL_PERMX_KIND_NLMSG:
> > +                               perm_str = CIL_KEY_NLMSG;
> > +                               break;
> >                         default:
> >                                 rc = SEPOL_ERR;
> >                                 goto exit;
> > @@ -4563,6 +4647,9 @@ static void __cil_print_permissionx(struct cil_permissionx *px)
> >                 case CIL_PERMX_KIND_IOCTL:
> >                         kind_str = CIL_KEY_IOCTL;
> >                         break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       kind_str = CIL_KEY_NLMSG;
> > +                       break;
> >                 default:
> >                         kind_str = "unknown";
> >                         break;
> > @@ -4696,8 +4783,21 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> >                         goto exit;
> >                 }
> >
> > -               rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > -               if (rc != SEPOL_OK) {
> > +               switch (cil_rule->perms.x.permx->kind) {
> > +               case CIL_PERMX_KIND_IOCTL:
> > +                       rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > +                       if (rc != SEPOL_OK) {
> > +                               goto exit;
> > +                       }
> > +                       break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
> > +                       if (rc != SEPOL_OK) {
> > +                               goto exit;
> > +                       }
> > +                       break;
> > +               default:
> > +                       rc = SEPOL_ERR;
> >                         goto exit;
> >                 }
> >
> > @@ -4715,13 +4815,7 @@ static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
> >         }
> >
> >  exit:
> > -       if (xperms != NULL) {
> > -               cil_list_for_each(item, xperms) {
> > -                       free(item->data);
> > -                       item->data = NULL;
> > -               }
> > -               cil_list_destroy(&xperms, CIL_FALSE);
> > -       }
> > +       __cil_cleanup_xperms(xperms);
> >
> >         rule->xperms = NULL;
> >         __cil_destroy_sepol_avrules(rule);
> > @@ -4904,6 +4998,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >         struct cil_list *neverallows = NULL;
> >         hashtab_t role_trans_table = NULL;
> >         hashtab_t avrulex_ioctl_table = NULL;
> > +       hashtab_t avrulex_nlmsg_table = NULL;
> >         void **type_value_to_cil = NULL;
> >         struct cil_class **class_value_to_cil = NULL;
> >         struct cil_perm ***perm_value_to_cil = NULL;
> > @@ -4947,7 +5042,13 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >
> >         avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> >         if (!avrulex_ioctl_table) {
> > -               cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
> > +               cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
> > +               goto exit;
> > +       }
> > +
> > +       avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
> > +       if (!avrulex_nlmsg_table) {
> > +               cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
> >                 goto exit;
> >         }
> >
> > @@ -4958,6 +5059,7 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >         extra_args.neverallows = neverallows;
> >         extra_args.role_trans_table = role_trans_table;
> >         extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
> > +       extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
> >         extra_args.type_value_to_cil = type_value_to_cil;
> >
> >         for (i = 1; i <= 3; i++) {
> > @@ -4980,7 +5082,12 @@ int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
> >                 if (i == 3) {
> >                         rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
> >                         if (rc != SEPOL_OK) {
> > -                               cil_log(CIL_INFO, "Failure creating avrulex rules\n");
> > +                               cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
> > +                               goto exit;
> > +                       }
> > +                       rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
> > +                       if (rc != SEPOL_OK) {
> > +                               cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
> >                                 goto exit;
> >                         }
> >                 }
> > @@ -5056,6 +5163,7 @@ exit:
> >         hashtab_destroy(role_trans_table);
> >         hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
> >         hashtab_destroy(avrulex_ioctl_table);
> > +       hashtab_destroy(avrulex_nlmsg_table);
> >         free(type_value_to_cil);
> >         free(class_value_to_cil);
> >         if (perm_value_to_cil != NULL) {
> > diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> > index 9c34be23..b1cfb4f0 100644
> > --- a/libsepol/cil/src/cil_build_ast.c
> > +++ b/libsepol/cil/src/cil_build_ast.c
> > @@ -2159,8 +2159,10 @@ int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
> >
> >         if (parse_current->data == CIL_KEY_IOCTL) {
> >                 permx->kind = CIL_PERMX_KIND_IOCTL;
> > +       } else if (parse_current->data == CIL_KEY_NLMSG) {
> > +               permx->kind = CIL_PERMX_KIND_NLMSG;
> >         } else {
> > -               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
> > +               cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
> >                 rc = SEPOL_ERR;
> >                 goto exit;
> >         }
> > diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> > index 6f1d3cb5..c968cf1e 100644
> > --- a/libsepol/cil/src/cil_internal.h
> > +++ b/libsepol/cil/src/cil_internal.h
> > @@ -236,6 +236,7 @@ extern char *CIL_KEY_DONTAUDITX;
> >  extern char *CIL_KEY_NEVERALLOWX;
> >  extern char *CIL_KEY_PERMISSIONX;
> >  extern char *CIL_KEY_IOCTL;
> > +extern char *CIL_KEY_NLMSG;
> >  extern char *CIL_KEY_UNORDERED;
> >  extern char *CIL_KEY_SRC_INFO;
> >  extern char *CIL_KEY_SRC_CIL;
> > @@ -623,6 +624,7 @@ struct cil_avrule {
> >  };
> >
> >  #define CIL_PERMX_KIND_IOCTL 1
> > +#define CIL_PERMX_KIND_NLMSG 2
> >  struct cil_permissionx {
> >         struct cil_symtab_datum datum;
> >         uint32_t kind;
> > diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> > index 7c543c47..2703d764 100644
> > --- a/libsepol/cil/src/cil_policy.c
> > +++ b/libsepol/cil/src/cil_policy.c
> > @@ -1112,6 +1112,8 @@ static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
> >
> >         if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> >                 kind = "ioctl";
> > +       } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +               kind = CIL_KEY_NLMSG;
> >         } else {
> >                 kind = "???";
> >         }
> > diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
> > index d994d717..67bdb0e9 100644
> > --- a/libsepol/cil/src/cil_verify.c
> > +++ b/libsepol/cil/src/cil_verify.c
> > @@ -1427,6 +1427,9 @@ int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
> >                 case CIL_PERMX_KIND_IOCTL:
> >                         kind_str = CIL_KEY_IOCTL;
> >                         break;
> > +               case CIL_PERMX_KIND_NLMSG:
> > +                       kind_str = CIL_KEY_NLMSG;
> > +                       break;
> >                 default:
> >                         cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
> >                         rc = SEPOL_ERR;
> > diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> > index d7f00bcc..b93a4fa1 100644
> > --- a/libsepol/cil/src/cil_write_ast.c
> > +++ b/libsepol/cil/src/cil_write_ast.c
> > @@ -303,7 +303,13 @@ static void write_permx(FILE *out, struct cil_permissionx *permx)
> >                 fprintf(out, "%s", datum_to_str(DATUM(permx)));
> >         } else {
> >                 fprintf(out, "(");
> > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > +                       fprintf(out, "%s ", "ioctl");
> > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +                       fprintf(out, "%s ", "nlmsg");
> > +               } else {
> > +                       fprintf(out, "%s ", "<?KIND>");
> > +               }
> >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> >                 write_expr(out, permx->expr_str);
> >                 fprintf(out, ")");
> > @@ -812,7 +818,13 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
> >         case CIL_PERMISSIONX: {
> >                 struct cil_permissionx *permx = node->data;
> >                 fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
> > -               fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
> > +               if (permx->kind == CIL_PERMX_KIND_IOCTL) {
> > +                       fprintf(out, "%s ", "ioctl");
> > +               } else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
> > +                       fprintf(out, "%s ", "nlmsg");
> > +               } else {
> > +                       fprintf(out, "%s ", "<?KIND>");
> > +               }
> >                 fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
> >                 write_expr(out, permx->expr_str);
> >                 fprintf(out, "))\n");
> > diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
> > index 10ecde9a..aa7481d3 100644
> > --- a/libsepol/include/sepol/policydb/avtab.h
> > +++ b/libsepol/include/sepol/policydb/avtab.h
> > @@ -74,6 +74,7 @@ typedef struct avtab_extended_perms {
> >
> >  #define AVTAB_XPERMS_IOCTLFUNCTION     0x01
> >  #define AVTAB_XPERMS_IOCTLDRIVER       0x02
> > +#define AVTAB_XPERMS_NLMSG             0x03
> >         /* extension of the avtab_key specified */
> >         uint8_t specified;
> >         uint8_t driver;
> > diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> > index 4bf9f05d..55918e1d 100644
> > --- a/libsepol/include/sepol/policydb/policydb.h
> > +++ b/libsepol/include/sepol/policydb/policydb.h
> > @@ -259,6 +259,7 @@ typedef struct class_perm_node {
> >  typedef struct av_extended_perms {
> >  #define AVRULE_XPERMS_IOCTLFUNCTION    0x01
> >  #define AVRULE_XPERMS_IOCTLDRIVER      0x02
> > +#define AVRULE_XPERMS_NLMSG            0x03
> >         uint8_t specified;
> >         uint8_t driver;
> >         /* 256 bits of permissions */
> > diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
> > index dd2749a0..47e45713 100644
> > --- a/libsepol/src/assertion.c
> > +++ b/libsepol/src/assertion.c
> > @@ -101,6 +101,9 @@ static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
> >         } else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
> >                         && (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
> >                 rc = extended_permissions_and(neverallow->perms, allow->perms);
> > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > +               rc = extended_permissions_and(neverallow->perms, allow->perms);
> >         }
> >
> >         return rc;
> > @@ -133,6 +136,12 @@ static void extended_permissions_violated(avtab_extended_perms_t *result,
> >                 result->specified = AVTAB_XPERMS_IOCTLDRIVER;
> >                 for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> >                         result->perms[i] = neverallow->perms[i] & allow->perms[i];
> > +       } else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
> > +                       && (allow->specified == AVTAB_XPERMS_NLMSG)) {
> > +               result->specified = AVTAB_XPERMS_NLMSG;
> > +               result->driver = 0;
> > +               for (i = 0; i < EXTENDED_PERMS_LEN; i++)
> > +                       result->perms[i] = neverallow->perms[i] & allow->perms[i];
> >         }
> >  }
> >
> > @@ -166,7 +175,8 @@ static int report_assertion_extended_permissions(sepol_handle_t *handle,
> >                              node = avtab_search_node_next(node, tmp_key.specified)) {
> >                                 xperms = node->datum.xperms;
> >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                                         continue;
> >
> >                                 rc = check_extended_permissions(avrule->xperms, xperms);
> > @@ -346,7 +356,8 @@ static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
> >                                 xperms = node->datum.xperms;
> >
> >                                 if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +                                               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +                                               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                                         continue;
> >                                 rc = check_extended_permissions(neverallow_xperms, xperms);
> >                                 if (rc)
> > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > index a6a466f7..959e6eb2 100644
> > --- a/libsepol/src/expand.c
> > +++ b/libsepol/src/expand.c
> > @@ -1796,6 +1796,9 @@ static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
> >         case AVRULE_XPERMS_IOCTLDRIVER:
> >                 xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
> >                 break;
> > +       case AVRULE_XPERMS_NLMSG:
> > +               xperms->specified = AVTAB_XPERMS_NLMSG;
> > +               break;
> >         default:
> >                 return -1;
> >         }
> > diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> > index 305567a5..090d4e43 100644
> > --- a/libsepol/src/kernel_to_cil.c
> > +++ b/libsepol/src/kernel_to_cil.c
> > @@ -1617,7 +1617,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> >         remaining = sizeof(xpermsbuf);
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG)) {
> >                 return NULL;
> >         }
> >
> > @@ -1637,7 +1638,8 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
> >                         continue;
> >                 }
> >
> > -               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
> > +               if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
> > +                       || xperms->specified & AVTAB_XPERMS_NLMSG) {
> >                         value = xperms->driver<<8 | bit;
> >                         if (in_range) {
> >                                 low_value = xperms->driver<<8 | low_bit;
> > @@ -1679,7 +1681,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> >  {
> >         uint32_t data = datum->data;
> >         type_datum_t *type;
> > -       const char *flavor, *tgt;
> > +       const char *flavor, *tgt, *func;
> >         char *src, *class, *perms, *new;
> >         char *rule = NULL;
> >
> > @@ -1742,8 +1744,21 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
> >                         goto exit;
> >                 }
> >
> > +               switch (datum->xperms->specified) {
> > +               case AVRULE_XPERMS_IOCTLDRIVER:
> > +               case AVRULE_XPERMS_IOCTLFUNCTION:
> > +                       func = "ioctl";
> > +                       break;
> > +               case AVRULE_XPERMS_NLMSG:
> > +                       func = "nlmsg";
> > +                       break;
> > +               default:
> > +                       sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
> > +                       goto exit;
> > +               }
> > +
> >                 rule = create_str("(%s %s %s (%s %s (%s)))", 6,
> > -                                 flavor, src, tgt, "ioctl", class, perms);
> > +                                 flavor, src, tgt, func, class, perms);
> >         } else {
> >                 new = pdb->p_type_val_to_name[data - 1];
> >
> > diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> > index 16e4004e..47d9b171 100644
> > --- a/libsepol/src/module_to_cil.c
> > +++ b/libsepol/src/module_to_cil.c
> > @@ -627,14 +627,15 @@ exit:
> >  static int xperms_to_cil(const av_extended_perms_t *xperms)
> >  {
> >         uint16_t value;
> > -       uint16_t low_bit;
> > +       uint16_t low_bit = 0;
> >         uint16_t low_value;
> >         unsigned int bit;
> >         unsigned int in_range = 0;
> >         int first = 1;
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                 return -1;
> >
> >         for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
> > @@ -674,6 +675,13 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> >                         } else {
> >                                 cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
> >                         }
> > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > +                       if (in_range) {
> > +                               cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
> > +                               in_range = 0;
> > +                       } else {
> > +                               cil_printf("0x%hx", (uint16_t) bit);
> > +                       }
> >                 }
> >         }
> >
> > @@ -683,7 +691,7 @@ static int xperms_to_cil(const av_extended_perms_t *xperms)
> >  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
> >  {
> >         int rc = -1;
> > -       const char *rule;
> > +       const char *rule, *func;
> >         const struct class_perm_node *classperm;
> >
> >         switch (type) {
> > @@ -705,10 +713,23 @@ static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
> >                 goto exit;
> >         }
> >
> > +       switch (xperms->specified) {
> > +       case AVRULE_XPERMS_IOCTLDRIVER:
> > +       case AVRULE_XPERMS_IOCTLFUNCTION:
> > +               func = "ioctl";
> > +               break;
> > +       case AVRULE_XPERMS_NLMSG:
> > +               func = "nlmsg";
> > +               break;
> > +       default:
> > +               log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
> > +               goto exit;
> > +       }
> > +
> >         for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
> >                 cil_indent(indent);
> >                 cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
> > -                          "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
> > +                          func, pdb->p_class_val_to_name[classperm->tclass - 1]);
> >                 xperms_to_cil(xperms);
> >                 cil_printf(")))\n");
> >         }
> > diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
> > index 6826155c..f23cfd17 100644
> > --- a/libsepol/src/optimize.c
> > +++ b/libsepol/src/optimize.c
> > @@ -180,6 +180,12 @@ static int process_avtab_datum(uint16_t specified,
> >
> >                         if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
> >                                 return process_xperms(x1->perms, x2->perms);
> > +               } else if (x1->specified == AVTAB_XPERMS_NLMSG) {
> > +                       if (x2->specified == AVTAB_XPERMS_NLMSG) {
> > +                               if (x1->driver != x2->driver)
> > +                                       return 0;
> > +                               return process_xperms(x1->perms, x2->perms);
> > +                       }
> >                 }
> >                 return 0;
> >         }
> > diff --git a/libsepol/src/util.c b/libsepol/src/util.c
> > index 902c63c5..b5b41f87 100644
> > --- a/libsepol/src/util.c
> > +++ b/libsepol/src/util.c
> > @@ -124,7 +124,7 @@ char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
> >  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >  {
> >         uint16_t value;
> > -       uint16_t low_bit;
> > +       uint16_t low_bit = 0;
> >         uint16_t low_value;
> >         unsigned int bit;
> >         unsigned int in_range = 0;
> > @@ -135,7 +135,8 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >         p = xpermsbuf;
> >
> >         if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
> > -               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
> > +               && (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
> > +               && (xperms->specified != AVTAB_XPERMS_NLMSG))
> >                 return NULL;
> >
> >         len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");
>
> Obviously, it won't always be "ioctl" now.
>
>
> > @@ -173,6 +174,12 @@ char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
> >                                 len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
> >                         }
> >
> > +               } else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
> > +                       if (in_range) {
> > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
> > +                       } else {
> > +                               len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
> > +                       }
> >                 }
> >
> >                 if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))
> > --
> > 2.34.0.rc0.344.g81b53c2807-goog
> >
>
> Something is not working correctly.
> I should be able to compile both a CIL and policy.conf and then
> convert them back to CIL and policy.conf. The CIL policy has nothing
> for the xperms, while the policy.conf doesn't even have the rules.
>
> So in the original CIL policy we have
> (allowx TYPE_X2 TYPE_OBJX2 (ioctl CLASS2 (0x1110)))
> (allowx TYPE_X3 TYPE_OBJX3 (nlmsg CLASS3 (0x1110)))
> The resulting policy, when it is converted back to CIL, is
> (allowx TYPE_X2 TYPE_OBJX2 (ioctl CLASS2 ((0x1110))))
> (allowx TYPE_X3 TYPE_OBJX3 (nlmsg CLASS3 ())))
>
> Using secil2conf generates the correct policy.conf from CIL, so I
> suspect something is wrong in reading or writing the binary policy.
>
> For the policy.conf, we have
> allowxperm TYPE_X2 TYPE_OBJX2:CLASS2 ioctl { 0x1110 };
> allowxperm TYPE_X3 TYPE_OBJX3:CLASS3 nlmsg { 0x1110 };
> The resulting policy, when converted back to a policy.conf is
> allowxperm TYPE_X2 TYPE_OBJX2:CLASS2 ioctl { 0x1110 };
>
> Using "checkpolicy -M -C -o test.conf.cil test.conf" results in the
> nlmsg rules being left out as well.
>
> I am investigating and I will let you know what I find.
>
> Jim
diff mbox series

Patch

diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
index d3eb6111..8ca22993 100644
--- a/checkpolicy/policy_define.c
+++ b/checkpolicy/policy_define.c
@@ -1818,27 +1818,27 @@  avrule_t *define_cond_pol_list(avrule_t * avlist, avrule_t * sl)
 	return sl;
 }
 
-typedef struct av_ioctl_range {
+typedef struct av_xperm_range {
 	uint16_t low;
 	uint16_t high;
-} av_ioctl_range_t;
+} av_xperm_range_t;
 
-struct av_ioctl_range_list {
+struct av_xperm_range_list {
 	uint8_t omit;
-	av_ioctl_range_t range;
-	struct av_ioctl_range_list *next;
+	av_xperm_range_t range;
+	struct av_xperm_range_list *next;
 };
 
-static int avrule_sort_ioctls(struct av_ioctl_range_list **rangehead)
+static int avrule_sort_xperms(struct av_xperm_range_list **rangehead)
 {
-	struct av_ioctl_range_list *r, *r2, *sorted, *sortedhead = NULL;
+	struct av_xperm_range_list *r, *r2, *sorted, *sortedhead = NULL;
 
 	/* order list by range.low */
 	for (r = *rangehead; r != NULL; r = r->next) {
-		sorted = malloc(sizeof(struct av_ioctl_range_list));
+		sorted = malloc(sizeof(struct av_xperm_range_list));
 		if (sorted == NULL)
 			goto error;
-		memcpy(sorted, r, sizeof(struct av_ioctl_range_list));
+		memcpy(sorted, r, sizeof(struct av_xperm_range_list));
 		sorted->next = NULL;
 		if (sortedhead == NULL) {
 			sortedhead = sorted;
@@ -1877,9 +1877,9 @@  error:
 	return -1;
 }
 
-static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
+static int avrule_merge_xperms(struct av_xperm_range_list **rangehead)
 {
-	struct av_ioctl_range_list *r, *tmp;
+	struct av_xperm_range_list *r, *tmp;
 	r = *rangehead;
 	while (r != NULL && r->next != NULL) {
 		/* merge */
@@ -1897,15 +1897,15 @@  static int avrule_merge_ioctls(struct av_ioctl_range_list **rangehead)
 	return 0;
 }
 
-static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
+static int avrule_read_xperm_ranges(struct av_xperm_range_list **rangehead)
 {
 	char *id;
-	struct av_ioctl_range_list *rnew, *r = NULL;
+	struct av_xperm_range_list *rnew, *r = NULL;
 	uint8_t omit = 0;
 
 	*rangehead = NULL;
 
-	/* read in all the ioctl commands */
+	/* read in all the netlink ranges / ioctl commands */
 	while ((id = queue_remove(id_queue))) {
 		if (strcmp(id,"~") == 0) {
 			/* these are values to be omitted */
@@ -1917,13 +1917,13 @@  static int avrule_read_ioctls(struct av_ioctl_range_list **rangehead)
 			id = queue_remove(id_queue);
 			r->range.high = (uint16_t) strtoul(id,NULL,0);
 			if (r->range.high < r->range.low) {
-				yyerror("Ioctl ranges must be in ascending order.");
+				yyerror("xperm ranges must be in ascending order.");
 				return -1;
 			}
 			free(id);
 		} else {
 			/* read in new low value */
-			rnew = malloc(sizeof(struct av_ioctl_range_list));
+			rnew = malloc(sizeof(struct av_xperm_range_list));
 			if (rnew == NULL)
 				goto error;
 			rnew->next = NULL;
@@ -1950,11 +1950,11 @@  error:
 }
 
 /* flip to included ranges */
-static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
+static int avrule_omit_xperms(struct av_xperm_range_list **rangehead)
 {
-	struct av_ioctl_range_list *rnew, *r, *newhead, *r2;
+	struct av_xperm_range_list *rnew, *r, *newhead, *r2;
 
-	rnew = calloc(1, sizeof(struct av_ioctl_range_list));
+	rnew = calloc(1, sizeof(struct av_xperm_range_list));
 	if (!rnew)
 		goto error;
 
@@ -1972,7 +1972,7 @@  static int avrule_omit_ioctls(struct av_ioctl_range_list **rangehead)
 
 	while (r) {
 		r2->range.high = r->range.low - 1;
-		rnew = calloc(1, sizeof(struct av_ioctl_range_list));
+		rnew = calloc(1, sizeof(struct av_xperm_range_list));
 		if (!rnew)
 			goto error;
 		r2->next = rnew;
@@ -1998,27 +1998,27 @@  error:
 	return -1;
 }
 
-static int avrule_ioctl_ranges(struct av_ioctl_range_list **rangelist)
+static int avrule_xperm_ranges(struct av_xperm_range_list **rangelist)
 {
-	struct av_ioctl_range_list *rangehead;
+	struct av_xperm_range_list *rangehead;
 	uint8_t omit;
 
 	/* read in ranges to include and omit */
-	if (avrule_read_ioctls(&rangehead))
+	if (avrule_read_xperm_ranges(&rangehead))
 		return -1;
 	if (rangehead == NULL) {
-		yyerror("error processing ioctl commands");
+		yyerror("error processing ioctl/netlink commands");
 		return -1;
 	}
 	omit = rangehead->omit;
-	/* sort and merge the input ioctls */
-	if (avrule_sort_ioctls(&rangehead))
+	/* sort and merge the input ranges */
+	if (avrule_sort_xperms(&rangehead))
 		return -1;
-	if (avrule_merge_ioctls(&rangehead))
+	if (avrule_merge_xperms(&rangehead))
 		return -1;
 	/* flip ranges if these are omitted */
 	if (omit) {
-		if (avrule_omit_ioctls(&rangehead))
+		if (avrule_omit_xperms(&rangehead))
 			return -1;
 	}
 
@@ -2189,11 +2189,11 @@  static int avrule_xperms_used(const av_extended_perms_t *xperms)
 #define IOC_DRIV(x) ((x) >> 8)
 #define IOC_FUNC(x) ((x) & 0xff)
 #define IOC_CMD(driver, func) (((driver) << 8) + (func))
-static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
+static int avrule_ioctl_partialdriver(struct av_xperm_range_list *rangelist,
 				av_extended_perms_t *complete_driver,
 				av_extended_perms_t **extended_perms)
 {
-	struct av_ioctl_range_list *r;
+	struct av_xperm_range_list *r;
 	av_extended_perms_t *xperms;
 	uint8_t low, high;
 
@@ -2228,10 +2228,10 @@  static int avrule_ioctl_partialdriver(struct av_ioctl_range_list *rangelist,
 
 }
 
-static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
+static int avrule_ioctl_completedriver(struct av_xperm_range_list *rangelist,
 			av_extended_perms_t **extended_perms)
 {
-	struct av_ioctl_range_list *r;
+	struct av_xperm_range_list *r;
 	av_extended_perms_t *xperms;
 	uint16_t low, high;
 	xperms = calloc(1, sizeof(av_extended_perms_t));
@@ -2270,10 +2270,10 @@  static int avrule_ioctl_completedriver(struct av_ioctl_range_list *rangelist,
 	return 0;
 }
 
-static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
+static int avrule_xperms_single_driver(struct av_xperm_range_list *rangelist,
 		av_extended_perms_t **extended_perms, unsigned int driver)
 {
-	struct av_ioctl_range_list *r;
+	struct av_xperm_range_list *r;
 	av_extended_perms_t *xperms;
 	uint16_t low, high;
 
@@ -2307,7 +2307,6 @@  static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
 		high = IOC_FUNC(high);
 		avrule_xperm_setrangebits(low, high, xperms);
 		xperms->driver = driver;
-		xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
 		r = r->next;
 	}
 
@@ -2320,6 +2319,18 @@  static int avrule_ioctl_func(struct av_ioctl_range_list *rangelist,
 	return 0;
 }
 
+void avrule_ioctl_freeranges(struct av_xperm_range_list *rangelist)
+{
+	struct av_xperm_range_list *r, *tmp;
+	
+	r = rangelist;
+	while (r) {
+		tmp = r;
+		r = r->next;
+		free(tmp);
+	}
+}
+
 static unsigned int xperms_for_each_bit(unsigned int *bit, av_extended_perms_t *xperms)
 {
 	unsigned int i;
@@ -2384,13 +2395,13 @@  static int avrule_cpy(avrule_t *dest, const avrule_t *src)
 static int define_te_avtab_ioctl(const avrule_t *avrule_template)
 {
 	avrule_t *avrule;
-	struct av_ioctl_range_list *rangelist, *r;
+	struct av_xperm_range_list *rangelist, *r;
 	av_extended_perms_t *complete_driver, *partial_driver, *xperms;
 	unsigned int i;
 
 
 	/* organize ioctl ranges */
-	if (avrule_ioctl_ranges(&rangelist))
+	if (avrule_xperm_ranges(&rangelist))
 		return -1;
 
 	/* create rule for ioctl driver types that are entirely enabled */
@@ -2422,10 +2433,11 @@  static int define_te_avtab_ioctl(const avrule_t *avrule_template)
 	 */
 	i = 0;
 	while (xperms_for_each_bit(&i, partial_driver)) {
-		if (avrule_ioctl_func(rangelist, &xperms, i))
+		if (avrule_xperms_single_driver(rangelist, &xperms, i))
 			return -1;
 
 		if (xperms) {
+			xperms->specified = AVRULE_XPERMS_IOCTLFUNCTION;
 			avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
 			if (!avrule) {
 				yyerror("out of memory");
@@ -2451,6 +2463,38 @@  done:
 	return 0;
 }
 
+int define_te_avtab_netlink(avrule_t *avrule_template)
+{
+	avrule_t *avrule;
+	struct av_xperm_range_list *range_list;
+	av_extended_perms_t *xperms = NULL;
+
+	/* organize message ranges */
+	if (avrule_xperm_ranges(&range_list))
+		return -1;
+
+	/* Netlink message types comfortably fit into a single driver
+	 * (see RTM_MAX in uapi/linux/rtnetlink.h)
+	 */
+	avrule_xperms_single_driver(range_list, &xperms, 0);
+
+	if (xperms && avrule_xperms_used(xperms)) {
+		xperms->specified = AVRULE_XPERMS_NLMSG;
+		avrule = (avrule_t *) calloc(1, sizeof(avrule_t));
+		if (!avrule) {
+			yyerror("out of memory");
+			return -1;
+		}
+		if (avrule_cpy(avrule, avrule_template))
+			return -1;
+		avrule->xperms = xperms;
+		append_avrule(avrule);
+	} else {
+		free(xperms);
+	}
+	return 0;
+}
+
 int define_te_avtab_extended_perms(int which)
 {
 	char *id;
@@ -2473,8 +2517,10 @@  int define_te_avtab_extended_perms(int which)
 	id = queue_remove(id_queue);
 	if (strcmp(id,"ioctl") == 0) {
 		rc = define_te_avtab_ioctl(avrule_template);
+	} else if (strcmp(id, "nlmsg") == 0) {
+		rc = define_te_avtab_netlink(avrule_template);
 	} else {
-		yyerror("only ioctl extended permissions are supported");
+		yyerror("only ioctl / nlmsg extended permissions are supported");
 		rc = -1;
 	}
 
diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
index ec2a3e9a..f8652ec5 100644
--- a/checkpolicy/test/dismod.c
+++ b/checkpolicy/test/dismod.c
@@ -296,6 +296,8 @@  static int display_avrule(avrule_t * avrule, policydb_t * policy,
 			xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
 		else if (avrule->xperms->specified == AVRULE_XPERMS_IOCTLDRIVER)
 			xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
+		else if (avrule->xperms->specified == AVRULE_XPERMS_NLMSG)
+			xperms.specified = AVTAB_XPERMS_NLMSG;
 		else {
 			fprintf(fp, "     ERROR: no valid xperms specified\n");
 			return -1;
diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
index 4cc7f87f..fbdb005a 100644
--- a/libsepol/cil/src/cil.c
+++ b/libsepol/cil/src/cil.c
@@ -219,6 +219,7 @@  char *CIL_KEY_DONTAUDITX;
 char *CIL_KEY_NEVERALLOWX;
 char *CIL_KEY_PERMISSIONX;
 char *CIL_KEY_IOCTL;
+char *CIL_KEY_NLMSG;
 char *CIL_KEY_UNORDERED;
 char *CIL_KEY_SRC_INFO;
 char *CIL_KEY_SRC_CIL;
@@ -388,6 +389,7 @@  static void cil_init_keys(void)
 	CIL_KEY_NEVERALLOWX = cil_strpool_add("neverallowx");
 	CIL_KEY_PERMISSIONX = cil_strpool_add("permissionx");
 	CIL_KEY_IOCTL = cil_strpool_add("ioctl");
+	CIL_KEY_NLMSG = cil_strpool_add("nlmsg");
 	CIL_KEY_UNORDERED = cil_strpool_add("unordered");
 	CIL_KEY_SRC_INFO = cil_strpool_add("<src_info>");
 	CIL_KEY_SRC_CIL = cil_strpool_add("cil");
diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
index d8aa495a..f9938d66 100644
--- a/libsepol/cil/src/cil_binary.c
+++ b/libsepol/cil/src/cil_binary.c
@@ -66,6 +66,7 @@  struct cil_args_binary {
 	int pass;
 	hashtab_t role_trans_table;
 	hashtab_t avrulex_ioctl_table;
+	hashtab_t avrulex_nlmsg_table;
 	void **type_value_to_cil;
 };
 
@@ -1553,7 +1554,7 @@  void __avrule_xperm_setrangebits(uint16_t low, uint16_t high, struct avtab_exten
 #define IOC_DRIV(x) (x >> 8)
 #define IOC_FUNC(x) (x & 0xff)
 
-int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
+int __cil_permx_ioctl_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
 {
 	ebitmap_node_t *node;
 	unsigned int i;
@@ -1618,13 +1619,53 @@  int __cil_permx_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list *
 	return SEPOL_OK;
 }
 
-int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
+int __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(ebitmap_t *xperms, struct cil_list **xperms_list)
+{
+	ebitmap_node_t *node;
+	uint16_t i;
+	uint16_t low;
+	struct avtab_extended_perms *avtab = NULL;
+	int start_new_range = 1;
+
+	cil_list_init(xperms_list, CIL_NONE);
+
+	ebitmap_for_each_positive_bit(xperms, node, i) {
+		if (start_new_range) {
+			low = i;
+			start_new_range = 0;
+		}
+
+		// Continue if the current bit isn't the end of the driver range
+		// or the next bit is set
+		if (ebitmap_get_bit(xperms, i + 1)) {
+			continue;
+		}
+
+		start_new_range = 1;
+
+		if (!avtab) {
+			avtab = cil_calloc(1, sizeof(*avtab));
+			// Netlink message types all fit in driver 0
+			avtab->driver = 0;
+			avtab->specified = AVTAB_XPERMS_NLMSG;
+		}
+
+		__avrule_xperm_setrangebits(low, i, avtab);
+	}
+
+	if (avtab) {
+		cil_list_append(*xperms_list, CIL_NONE, avtab);
+	}
+
+	return SEPOL_OK;
+}
+
+int __cil_avrulex_to_policydb(hashtab_key_t k, struct cil_list* xperms_list, char* cil_key, void *args)
 {
 	int rc = SEPOL_OK;
 	struct policydb *pdb;
 	avtab_key_t *avtab_key;
 	avtab_datum_t avtab_datum;
-	struct cil_list *xperms_list = NULL;
 	struct cil_list_item *item;
 	class_datum_t *sepol_obj;
 	uint32_t data = 0;
@@ -1637,17 +1678,12 @@  int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
 	// setting the data for an extended avtab isn't really necessary because
 	// it is ignored by the kernel. However, neverallow checking requires that
 	// the data value be set, so set it for that to work.
-	rc = __perm_str_to_datum(CIL_KEY_IOCTL, sepol_obj, &data);
+	rc = __perm_str_to_datum(cil_key, sepol_obj, &data);
 	if (rc != SEPOL_OK) {
 		goto exit;
 	}
 	avtab_datum.data = data;
 
-	rc = __cil_permx_bitmap_to_sepol_xperms_list(datum, &xperms_list);
-	if (rc != SEPOL_OK) {
-		goto exit;
-	}
-
 	cil_list_for_each(item, xperms_list) {
 		avtab_datum.xperms = item->data;
 		rc = avtab_insert(&pdb->te_avtab, avtab_key, &avtab_datum);
@@ -1659,16 +1695,57 @@  int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void
 	rc = SEPOL_OK;
 
 exit:
+	return rc;
+}
+
+void __cil_cleanup_xperms(struct cil_list *xperms_list)
+{
+	struct cil_list_item *item;
+
 	if (xperms_list != NULL) {
 		cil_list_for_each(item, xperms_list) {
 			free(item->data);
+			item->data = NULL;
 		}
 		cil_list_destroy(&xperms_list, CIL_FALSE);
 	}
+}
+
+int __cil_avrulex_ioctl_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
+{
+	struct cil_list *xperms_list = NULL;
+	int rc;
+
+	rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(datum, &xperms_list);
+	if (rc != SEPOL_OK) {
+		goto exit;
+	}
+	rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_IOCTL, args);
+
+exit:
+	__cil_cleanup_xperms(xperms_list);
+
 	return rc;
 }
 
-int __cil_avrulex_ioctl_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
+int __cil_avrulex_nlmsg_to_policydb(hashtab_key_t k, hashtab_datum_t datum, void *args)
+{
+	struct cil_list *xperms_list = NULL;
+	int rc;
+
+	rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(datum, &xperms_list);
+	if (rc != SEPOL_OK) {
+		goto exit;
+	}
+	rc = __cil_avrulex_to_policydb(k, xperms_list, CIL_KEY_NLMSG, args);
+
+exit:
+	__cil_cleanup_xperms(xperms_list);
+
+	return rc;
+}
+
+int __cil_avrulex_to_hashtable(hashtab_t h, uint16_t kind, uint32_t src, uint32_t tgt, uint32_t obj, ebitmap_t *xperms)
 {
 	uint16_t specified;
 	avtab_key_t *avtab_key;
@@ -1747,8 +1824,12 @@  int __cil_avrulex_to_hashtable_helper(policydb_t *pdb, uint16_t kind, struct cil
 		if (rc != SEPOL_OK) goto exit;
 
 		switch (permx->kind) {
-		case  CIL_PERMX_KIND_IOCTL:
-			rc = __cil_avrulex_ioctl_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
+		case CIL_PERMX_KIND_IOCTL:
+			rc = __cil_avrulex_to_hashtable(args->avrulex_ioctl_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
+			if (rc != SEPOL_OK) goto exit;
+			break;
+		case CIL_PERMX_KIND_NLMSG:
+			rc = __cil_avrulex_to_hashtable(args->avrulex_nlmsg_table, kind, sepol_src->s.value, sepol_tgt->s.value, sepol_obj->s.value, permx->perms);
 			if (rc != SEPOL_OK) goto exit;
 			break;
 		default:
@@ -4417,6 +4498,9 @@  static int __cil_permx_to_sepol_class_perms(policydb_t *pdb, struct cil_permissi
 			case CIL_PERMX_KIND_IOCTL:
 				perm_str = CIL_KEY_IOCTL;
 				break;
+			case CIL_PERMX_KIND_NLMSG:
+				perm_str = CIL_KEY_NLMSG;
+				break;
 			default:
 				rc = SEPOL_ERR;
 				goto exit;
@@ -4563,6 +4647,9 @@  static void __cil_print_permissionx(struct cil_permissionx *px)
 		case CIL_PERMX_KIND_IOCTL:
 			kind_str = CIL_KEY_IOCTL;
 			break;
+		case CIL_PERMX_KIND_NLMSG:
+			kind_str = CIL_KEY_NLMSG;
+			break;
 		default:
 			kind_str = "unknown";
 			break;
@@ -4696,8 +4783,21 @@  static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
 			goto exit;
 		}
 
-		rc = __cil_permx_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
-		if (rc != SEPOL_OK) {
+		switch (cil_rule->perms.x.permx->kind) {
+		case CIL_PERMX_KIND_IOCTL:
+			rc = __cil_permx_ioctl_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
+			if (rc != SEPOL_OK) {
+				goto exit;
+			}
+			break;
+		case CIL_PERMX_KIND_NLMSG:
+			rc = __cil_permx_nlmsg_bitmap_to_sepol_xperms_list(cil_rule->perms.x.permx->perms, &xperms);
+			if (rc != SEPOL_OK) {
+				goto exit;
+			}
+			break;
+		default:
+			rc = SEPOL_ERR;
 			goto exit;
 		}
 
@@ -4715,13 +4815,7 @@  static int cil_check_neverallow(const struct cil_db *db, policydb_t *pdb, struct
 	}
 
 exit:
-	if (xperms != NULL) {
-		cil_list_for_each(item, xperms) {
-			free(item->data);
-			item->data = NULL;
-		}
-		cil_list_destroy(&xperms, CIL_FALSE);
-	}
+	__cil_cleanup_xperms(xperms);
 
 	rule->xperms = NULL;
 	__cil_destroy_sepol_avrules(rule);
@@ -4904,6 +4998,7 @@  int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
 	struct cil_list *neverallows = NULL;
 	hashtab_t role_trans_table = NULL;
 	hashtab_t avrulex_ioctl_table = NULL;
+	hashtab_t avrulex_nlmsg_table = NULL;
 	void **type_value_to_cil = NULL;
 	struct cil_class **class_value_to_cil = NULL;
 	struct cil_perm ***perm_value_to_cil = NULL;
@@ -4947,7 +5042,13 @@  int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
 
 	avrulex_ioctl_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
 	if (!avrulex_ioctl_table) {
-		cil_log(CIL_INFO, "Failure to create hashtab for avrulex\n");
+		cil_log(CIL_INFO, "Failure to create hashtab for ioctl\n");
+		goto exit;
+	}
+
+	avrulex_nlmsg_table = hashtab_create(avrulex_hash, avrulex_compare, AVRULEX_TABLE_SIZE);
+	if (!avrulex_nlmsg_table) {
+		cil_log(CIL_INFO, "Failure to create hashtab for nlmsg\n");
 		goto exit;
 	}
 
@@ -4958,6 +5059,7 @@  int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
 	extra_args.neverallows = neverallows;
 	extra_args.role_trans_table = role_trans_table;
 	extra_args.avrulex_ioctl_table = avrulex_ioctl_table;
+	extra_args.avrulex_nlmsg_table = avrulex_nlmsg_table;
 	extra_args.type_value_to_cil = type_value_to_cil;
 
 	for (i = 1; i <= 3; i++) {
@@ -4980,7 +5082,12 @@  int cil_binary_create_allocated_pdb(const struct cil_db *db, sepol_policydb_t *p
 		if (i == 3) {
 			rc = hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_to_policydb, pdb);
 			if (rc != SEPOL_OK) {
-				cil_log(CIL_INFO, "Failure creating avrulex rules\n");
+				cil_log(CIL_INFO, "Failure creating ioctl avrulex rules\n");
+				goto exit;
+			}
+			rc = hashtab_map(avrulex_nlmsg_table, __cil_avrulex_nlmsg_to_policydb, pdb);
+			if (rc != SEPOL_OK) {
+				cil_log(CIL_INFO, "Failure creating nlmsg avrulex rules\n");
 				goto exit;
 			}
 		}
@@ -5056,6 +5163,7 @@  exit:
 	hashtab_destroy(role_trans_table);
 	hashtab_map(avrulex_ioctl_table, __cil_avrulex_ioctl_destroy, NULL);
 	hashtab_destroy(avrulex_ioctl_table);
+	hashtab_destroy(avrulex_nlmsg_table);
 	free(type_value_to_cil);
 	free(class_value_to_cil);
 	if (perm_value_to_cil != NULL) {
diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
index 9c34be23..b1cfb4f0 100644
--- a/libsepol/cil/src/cil_build_ast.c
+++ b/libsepol/cil/src/cil_build_ast.c
@@ -2159,8 +2159,10 @@  int cil_fill_permissionx(struct cil_tree_node *parse_current, struct cil_permiss
 
 	if (parse_current->data == CIL_KEY_IOCTL) {
 		permx->kind = CIL_PERMX_KIND_IOCTL;
+	} else if (parse_current->data == CIL_KEY_NLMSG) {
+		permx->kind = CIL_PERMX_KIND_NLMSG;
 	} else {
-		cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be \"ioctl\"\n", (char *)parse_current->data);
+		cil_log(CIL_ERR, "Unknown permissionx kind, %s. Must be one of [%s, %s]\n", (char *)parse_current->data, CIL_KEY_IOCTL, CIL_KEY_NLMSG);
 		rc = SEPOL_ERR;
 		goto exit;
 	}
diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
index 6f1d3cb5..c968cf1e 100644
--- a/libsepol/cil/src/cil_internal.h
+++ b/libsepol/cil/src/cil_internal.h
@@ -236,6 +236,7 @@  extern char *CIL_KEY_DONTAUDITX;
 extern char *CIL_KEY_NEVERALLOWX;
 extern char *CIL_KEY_PERMISSIONX;
 extern char *CIL_KEY_IOCTL;
+extern char *CIL_KEY_NLMSG;
 extern char *CIL_KEY_UNORDERED;
 extern char *CIL_KEY_SRC_INFO;
 extern char *CIL_KEY_SRC_CIL;
@@ -623,6 +624,7 @@  struct cil_avrule {
 };
 
 #define CIL_PERMX_KIND_IOCTL 1
+#define CIL_PERMX_KIND_NLMSG 2
 struct cil_permissionx {
 	struct cil_symtab_datum datum;
 	uint32_t kind;
diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
index 7c543c47..2703d764 100644
--- a/libsepol/cil/src/cil_policy.c
+++ b/libsepol/cil/src/cil_policy.c
@@ -1112,6 +1112,8 @@  static void cil_xperms_to_policy(FILE *out, struct cil_permissionx *permx)
 
 	if (permx->kind == CIL_PERMX_KIND_IOCTL) {
 		kind = "ioctl";
+	} else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
+		kind = CIL_KEY_NLMSG;
 	} else {
 		kind = "???";
 	}
diff --git a/libsepol/cil/src/cil_verify.c b/libsepol/cil/src/cil_verify.c
index d994d717..67bdb0e9 100644
--- a/libsepol/cil/src/cil_verify.c
+++ b/libsepol/cil/src/cil_verify.c
@@ -1427,6 +1427,9 @@  int __cil_verify_permissionx(struct cil_permissionx *permx, struct cil_tree_node
 		case CIL_PERMX_KIND_IOCTL:
 			kind_str = CIL_KEY_IOCTL;
 			break;
+		case CIL_PERMX_KIND_NLMSG:
+			kind_str = CIL_KEY_NLMSG;
+			break;
 		default:
 			cil_tree_log(node, CIL_ERR, "Invalid permissionx kind (%d)", permx->kind);
 			rc = SEPOL_ERR;
diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
index d7f00bcc..b93a4fa1 100644
--- a/libsepol/cil/src/cil_write_ast.c
+++ b/libsepol/cil/src/cil_write_ast.c
@@ -303,7 +303,13 @@  static void write_permx(FILE *out, struct cil_permissionx *permx)
 		fprintf(out, "%s", datum_to_str(DATUM(permx)));
 	} else {
 		fprintf(out, "(");
-		fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
+		if (permx->kind == CIL_PERMX_KIND_IOCTL) {
+			fprintf(out, "%s ", "ioctl");
+		} else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
+			fprintf(out, "%s ", "nlmsg");
+		} else {
+			fprintf(out, "%s ", "<?KIND>");
+		}
 		fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
 		write_expr(out, permx->expr_str);
 		fprintf(out, ")");
@@ -812,7 +818,13 @@  void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
 	case CIL_PERMISSIONX: {
 		struct cil_permissionx *permx = node->data;
 		fprintf(out, "(permissionx %s (", datum_to_str(DATUM(permx)));
-		fprintf(out, "%s ", permx->kind == CIL_PERMX_KIND_IOCTL ? "ioctl" : "<?KIND>");
+		if (permx->kind == CIL_PERMX_KIND_IOCTL) {
+			fprintf(out, "%s ", "ioctl");
+		} else if (permx->kind == CIL_PERMX_KIND_NLMSG) {
+			fprintf(out, "%s ", "nlmsg");
+		} else {
+			fprintf(out, "%s ", "<?KIND>");
+		}
 		fprintf(out, "%s ", datum_or_str(DATUM(permx->obj), permx->obj_str));
 		write_expr(out, permx->expr_str);
 		fprintf(out, "))\n");
diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
index 10ecde9a..aa7481d3 100644
--- a/libsepol/include/sepol/policydb/avtab.h
+++ b/libsepol/include/sepol/policydb/avtab.h
@@ -74,6 +74,7 @@  typedef struct avtab_extended_perms {
 
 #define AVTAB_XPERMS_IOCTLFUNCTION	0x01
 #define AVTAB_XPERMS_IOCTLDRIVER	0x02
+#define AVTAB_XPERMS_NLMSG		0x03
 	/* extension of the avtab_key specified */
 	uint8_t specified;
 	uint8_t driver;
diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
index 4bf9f05d..55918e1d 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -259,6 +259,7 @@  typedef struct class_perm_node {
 typedef struct av_extended_perms {
 #define AVRULE_XPERMS_IOCTLFUNCTION	0x01
 #define AVRULE_XPERMS_IOCTLDRIVER	0x02
+#define AVRULE_XPERMS_NLMSG		0x03
 	uint8_t specified;
 	uint8_t driver;
 	/* 256 bits of permissions */
diff --git a/libsepol/src/assertion.c b/libsepol/src/assertion.c
index dd2749a0..47e45713 100644
--- a/libsepol/src/assertion.c
+++ b/libsepol/src/assertion.c
@@ -101,6 +101,9 @@  static int check_extended_permissions(av_extended_perms_t *neverallow, avtab_ext
 	} else if ((neverallow->specified == AVRULE_XPERMS_IOCTLDRIVER)
 			&& (allow->specified == AVTAB_XPERMS_IOCTLDRIVER)) {
 		rc = extended_permissions_and(neverallow->perms, allow->perms);
+	} else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
+			&& (allow->specified == AVTAB_XPERMS_NLMSG)) {
+		rc = extended_permissions_and(neverallow->perms, allow->perms);
 	}
 
 	return rc;
@@ -133,6 +136,12 @@  static void extended_permissions_violated(avtab_extended_perms_t *result,
 		result->specified = AVTAB_XPERMS_IOCTLDRIVER;
 		for (i = 0; i < EXTENDED_PERMS_LEN; i++)
 			result->perms[i] = neverallow->perms[i] & allow->perms[i];
+	} else if ((neverallow->specified == AVRULE_XPERMS_NLMSG)
+			&& (allow->specified == AVTAB_XPERMS_NLMSG)) {
+		result->specified = AVTAB_XPERMS_NLMSG;
+		result->driver = 0;
+		for (i = 0; i < EXTENDED_PERMS_LEN; i++)
+			result->perms[i] = neverallow->perms[i] & allow->perms[i];
 	}
 }
 
@@ -166,7 +175,8 @@  static int report_assertion_extended_permissions(sepol_handle_t *handle,
 			     node = avtab_search_node_next(node, tmp_key.specified)) {
 				xperms = node->datum.xperms;
 				if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
-						&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
+						&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
+						&& (xperms->specified != AVTAB_XPERMS_NLMSG))
 					continue;
 
 				rc = check_extended_permissions(avrule->xperms, xperms);
@@ -346,7 +356,8 @@  static int check_assertion_extended_permissions_avtab(avrule_t *avrule, avtab_t
 				xperms = node->datum.xperms;
 
 				if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
-						&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
+						&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
+						&& (xperms->specified != AVTAB_XPERMS_NLMSG))
 					continue;
 				rc = check_extended_permissions(neverallow_xperms, xperms);
 				if (rc)
diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
index a6a466f7..959e6eb2 100644
--- a/libsepol/src/expand.c
+++ b/libsepol/src/expand.c
@@ -1796,6 +1796,9 @@  static int allocate_xperms(sepol_handle_t * handle, avtab_datum_t * avdatump,
 	case AVRULE_XPERMS_IOCTLDRIVER:
 		xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
 		break;
+	case AVRULE_XPERMS_NLMSG:
+		xperms->specified = AVTAB_XPERMS_NLMSG;
+		break;
 	default:
 		return -1;
 	}
diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
index 305567a5..090d4e43 100644
--- a/libsepol/src/kernel_to_cil.c
+++ b/libsepol/src/kernel_to_cil.c
@@ -1617,7 +1617,8 @@  static char *xperms_to_str(avtab_extended_perms_t *xperms)
 	remaining = sizeof(xpermsbuf);
 
 	if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
-		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)) {
+		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
+		&& (xperms->specified != AVTAB_XPERMS_NLMSG)) {
 		return NULL;
 	}
 
@@ -1637,7 +1638,8 @@  static char *xperms_to_str(avtab_extended_perms_t *xperms)
 			continue;
 		}
 
-		if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
+		if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION
+			|| xperms->specified & AVTAB_XPERMS_NLMSG) {
 			value = xperms->driver<<8 | bit;
 			if (in_range) {
 				low_value = xperms->driver<<8 | low_bit;
@@ -1679,7 +1681,7 @@  static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 {
 	uint32_t data = datum->data;
 	type_datum_t *type;
-	const char *flavor, *tgt;
+	const char *flavor, *tgt, *func;
 	char *src, *class, *perms, *new;
 	char *rule = NULL;
 
@@ -1742,8 +1744,21 @@  static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 			goto exit;
 		}
 
+		switch (datum->xperms->specified) {
+		case AVRULE_XPERMS_IOCTLDRIVER:
+		case AVRULE_XPERMS_IOCTLFUNCTION:
+			func = "ioctl";
+			break;
+		case AVRULE_XPERMS_NLMSG:
+			func = "nlmsg";
+			break;
+		default:
+			sepol_log_err("Unexpected xperm spec: %hhu", datum->xperms->specified);
+			goto exit;
+		}
+
 		rule = create_str("(%s %s %s (%s %s (%s)))", 6,
-				  flavor, src, tgt, "ioctl", class, perms);
+				  flavor, src, tgt, func, class, perms);
 	} else {
 		new = pdb->p_type_val_to_name[data - 1];
 
diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
index 16e4004e..47d9b171 100644
--- a/libsepol/src/module_to_cil.c
+++ b/libsepol/src/module_to_cil.c
@@ -627,14 +627,15 @@  exit:
 static int xperms_to_cil(const av_extended_perms_t *xperms)
 {
 	uint16_t value;
-	uint16_t low_bit;
+	uint16_t low_bit = 0;
 	uint16_t low_value;
 	unsigned int bit;
 	unsigned int in_range = 0;
 	int first = 1;
 
 	if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
-		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
+		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
+		&& (xperms->specified != AVTAB_XPERMS_NLMSG))
 		return -1;
 
 	for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
@@ -674,6 +675,13 @@  static int xperms_to_cil(const av_extended_perms_t *xperms)
 			} else {
 				cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
 			}
+		} else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
+			if (in_range) {
+				cil_printf("(range 0x%hx 0x%hx)", low_bit, (uint16_t) bit);
+				in_range = 0;
+			} else {
+				cil_printf("0x%hx", (uint16_t) bit);
+			}
 		}
 	}
 
@@ -683,7 +691,7 @@  static int xperms_to_cil(const av_extended_perms_t *xperms)
 static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
 {
 	int rc = -1;
-	const char *rule;
+	const char *rule, *func;
 	const struct class_perm_node *classperm;
 
 	switch (type) {
@@ -705,10 +713,23 @@  static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const
 		goto exit;
 	}
 
+	switch (xperms->specified) {
+	case AVRULE_XPERMS_IOCTLDRIVER:
+	case AVRULE_XPERMS_IOCTLFUNCTION:
+		func = "ioctl";
+		break;
+	case AVRULE_XPERMS_NLMSG:
+		func = "nlmsg";
+		break;
+	default:
+		log_err("Unexpected xperm spec for %s, %s, %s: %hhu", rule, src, tgt, xperms->specified);
+		goto exit;
+	}
+
 	for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
 		cil_indent(indent);
 		cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
-			   "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
+			   func, pdb->p_class_val_to_name[classperm->tclass - 1]);
 		xperms_to_cil(xperms);
 		cil_printf(")))\n");
 	}
diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
index 6826155c..f23cfd17 100644
--- a/libsepol/src/optimize.c
+++ b/libsepol/src/optimize.c
@@ -180,6 +180,12 @@  static int process_avtab_datum(uint16_t specified,
 
 			if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER)
 				return process_xperms(x1->perms, x2->perms);
+		} else if (x1->specified == AVTAB_XPERMS_NLMSG) {
+			if (x2->specified == AVTAB_XPERMS_NLMSG) {
+				if (x1->driver != x2->driver)
+					return 0;
+				return process_xperms(x1->perms, x2->perms);
+			}
 		}
 		return 0;
 	}
diff --git a/libsepol/src/util.c b/libsepol/src/util.c
index 902c63c5..b5b41f87 100644
--- a/libsepol/src/util.c
+++ b/libsepol/src/util.c
@@ -124,7 +124,7 @@  char *sepol_av_to_string(policydb_t * policydbp, uint32_t tclass,
 char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
 {
 	uint16_t value;
-	uint16_t low_bit;
+	uint16_t low_bit = 0;
 	uint16_t low_value;
 	unsigned int bit;
 	unsigned int in_range = 0;
@@ -135,7 +135,8 @@  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
 	p = xpermsbuf;
 
 	if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
-		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
+		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER)
+		&& (xperms->specified != AVTAB_XPERMS_NLMSG))
 		return NULL;
 
 	len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "ioctl { ");
@@ -173,6 +174,12 @@  char *sepol_extended_perms_to_string(avtab_extended_perms_t *xperms)
 				len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", value, (uint16_t) (value|0xff));
 			}
 
+		} else if (xperms->specified & AVTAB_XPERMS_NLMSG) {
+			if (in_range) {
+				len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx-0x%hx ", low_bit, (uint16_t) bit);
+			} else {
+				len = snprintf(p, sizeof(xpermsbuf) - xpermslen, "0x%hx ", (uint16_t) bit);
+			}
 		}
 
 		if (len < 0 || (size_t) len >= (sizeof(xpermsbuf) - xpermslen))