nftables: add support for setting secmark

Message ID 20180920074322.5045-1-cgzones@googlemail.com
State Accepted
Delegated to: Pablo Neira
Headers show
Series
  • nftables: add support for setting secmark
Related show

Commit Message

Christian Göttsche Sept. 20, 2018, 7:43 a.m.
Add support for new nft object secmark holding security context strings.

The following should demonstrate its usage (based on SELinux context):

    # define a tag containing a context string
    nft add secmark inet filter sshtag \"system_u:object_r:ssh_server_packet_t:s0\"
    nft list secmarks

    # set the secmark
    nft add rule inet filter input tcp dport 22 meta secmark set sshtag

    # map usage
    nft add map inet filter secmapping { type inet_service : secmark \; }
    nft add element inet filter secmapping { 22 : sshtag }
    nft list maps
    nft list map inet filter secmapping
    nft add rule inet filter input meta secmark set tcp dport map @secmapping

Based on v0.9.0

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 include/linux/netfilter/nf_tables.h |  18 ++++-
 include/rule.h                      |   9 +++
 src/evaluate.c                      |   5 ++
 src/json.c                          |  10 +++
 src/meta.c                          |   1 +
 src/netlink.c                       |   8 +++
 src/netlink_delinearize.c           |   2 +-
 src/parser_bison.y                  | 104 ++++++++++++++++++++++++++--
 src/parser_json.c                   |  19 ++++-
 src/rule.c                          |  16 +++++
 src/scanner.l                       |   3 +
 src/statement.c                     |   1 +
 12 files changed, 189 insertions(+), 7 deletions(-)

Comments

Pablo Neira Ayuso Oct. 8, 2018, 10:15 p.m. | #1
On Thu, Sep 20, 2018 at 09:43:22AM +0200, Christian Göttsche wrote:
> Add support for new nft object secmark holding security context strings.
> 
> The following should demonstrate its usage (based on SELinux context):
> 
>     # define a tag containing a context string
>     nft add secmark inet filter sshtag \"system_u:object_r:ssh_server_packet_t:s0\"
>     nft list secmarks
> 
>     # set the secmark
>     nft add rule inet filter input tcp dport 22 meta secmark set sshtag
> 
>     # map usage
>     nft add map inet filter secmapping { type inet_service : secmark \; }
>     nft add element inet filter secmapping { 22 : sshtag }
>     nft list maps
>     nft list map inet filter secmapping
>     nft add rule inet filter input meta secmark set tcp dport map @secmapping
> 
> Based on v0.9.0

Thanks!

Could you rebase on top of git.netfilter.org/nftables?

It would also great if you can update tests/py/ to do some minimal
regression testing. Thanks.
Pablo Neira Ayuso Oct. 15, 2018, 12:26 p.m. | #2
On Thu, Sep 20, 2018 at 09:43:22AM +0200, Christian Göttsche wrote:
> Add support for new nft object secmark holding security context strings.
> 
> The following should demonstrate its usage (based on SELinux context):
> 
>     # define a tag containing a context string
>     nft add secmark inet filter sshtag \"system_u:object_r:ssh_server_packet_t:s0\"
>     nft list secmarks
> 
>     # set the secmark
>     nft add rule inet filter input tcp dport 22 meta secmark set sshtag
> 
>     # map usage
>     nft add map inet filter secmapping { type inet_service : secmark \; }
>     nft add element inet filter secmapping { 22 : sshtag }
>     nft list maps
>     nft list map inet filter secmapping
>     nft add rule inet filter input meta secmark set tcp dport map @secmapping
> 
> Based on v0.9.0

I made the rebase myself and pushed it out.

I'd appreciate if you can review what we have in nft.git and test it.

Thanks.

Patch

diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index 88e0ca1..e834e51 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -1150,6 +1150,21 @@  enum nft_quota_attributes {
 };
 #define NFTA_QUOTA_MAX		(__NFTA_QUOTA_MAX - 1)
 
+/**
+ * enum nft_secmark_attributes - nf_tables secmark expression netlink attributes
+ *
+ * @NFTA_SECMARK_CTX: security context (NLA_STRING)
+ */
+enum nft_secmark_attributes {
+	NFTA_SECMARK_UNSPEC,
+	NFTA_SECMARK_CTX,
+	__NFTA_SECMARK_MAX,
+};
+#define NFTA_SECMARK_MAX	(__NFTA_SECMARK_MAX - 1)
+
+/* Max security context length */
+#define NFT_SECMARK_CTX_MAXLEN		256
+
 /**
  * enum nft_reject_types - nf_tables reject expression reject types
  *
@@ -1379,7 +1394,8 @@  enum nft_ct_helper_attributes {
 #define NFT_OBJECT_CT_HELPER	3
 #define NFT_OBJECT_LIMIT	4
 #define NFT_OBJECT_CONNLIMIT	5
-#define __NFT_OBJECT_MAX	6
+#define NFT_OBJECT_SECMARK	6
+#define __NFT_OBJECT_MAX	7
 #define NFT_OBJECT_MAX		(__NFT_OBJECT_MAX - 1)
 
 /**
diff --git a/include/rule.h b/include/rule.h
index 909ff36..76ac0ab 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -316,6 +316,10 @@  struct limit {
 	uint32_t	flags;
 };
 
+struct secmark {
+	char		ctx[NFT_SECMARK_CTX_MAXLEN];
+};
+
 /**
  * struct obj - nftables stateful object statement
  *
@@ -336,6 +340,7 @@  struct obj {
 		struct quota		quota;
 		struct ct_helper	ct_helper;
 		struct limit		limit;
+		struct secmark		secmark;
 	};
 };
 
@@ -434,6 +439,8 @@  enum cmd_ops {
  * @CMD_OBJ_LIMIT:	limit
  * @CMD_OBJ_LIMITS:	multiple limits
  * @CMD_OBJ_FLOWTABLES:	flow tables
+ * @CMD_OBJ_SECMARK:	secmark
+ * @CMD_OBJ_SECMARKS:	multiple secmarks
  */
 enum cmd_obj {
 	CMD_OBJ_INVALID,
@@ -462,6 +469,8 @@  enum cmd_obj {
 	CMD_OBJ_LIMITS,
 	CMD_OBJ_FLOWTABLE,
 	CMD_OBJ_FLOWTABLES,
+	CMD_OBJ_SECMARK,
+	CMD_OBJ_SECMARKS,
 };
 
 struct markup {
diff --git a/src/evaluate.c b/src/evaluate.c
index c4ee3cc..d7d2775 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -3131,6 +3131,7 @@  static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
 	case CMD_OBJ_LIMIT:
+	case CMD_OBJ_SECMARK:
 		return 0;
 	default:
 		BUG("invalid command object type %u\n", cmd->obj);
@@ -3158,6 +3159,7 @@  static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
 	case CMD_OBJ_LIMIT:
+	case CMD_OBJ_SECMARK:
 		return 0;
 	default:
 		BUG("invalid command object type %u\n", cmd->obj);
@@ -3291,12 +3293,15 @@  static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd)
 		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
 	case CMD_OBJ_LIMIT:
 		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_LIMIT);
+	case CMD_OBJ_SECMARK:
+		return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_SECMARK);
 	case CMD_OBJ_COUNTERS:
 	case CMD_OBJ_QUOTAS:
 	case CMD_OBJ_CT_HELPERS:
 	case CMD_OBJ_LIMITS:
 	case CMD_OBJ_SETS:
 	case CMD_OBJ_FLOWTABLES:
+	case CMD_OBJ_SECMARKS:
 		if (cmd->handle.table.name == NULL)
 			return 0;
 		if (table_lookup(&cmd->handle, ctx->cache) == NULL)
diff --git a/src/json.c b/src/json.c
index b6e6ca9..a6fe334 100644
--- a/src/json.c
+++ b/src/json.c
@@ -263,6 +263,12 @@  static json_t *obj_print_json(struct output_ctx *octx, const struct obj *obj)
 		json_object_update(root, tmp);
 		json_decref(tmp);
 		break;
+	case NFT_OBJECT_SECMARK:
+		tmp = json_pack("{s:s}",
+				"context", obj->secmark.ctx);
+		json_object_update(root, tmp);
+		json_decref(tmp);
+		break;
 	case NFT_OBJECT_CT_HELPER:
 		type = "ct helper";
 		tmp = json_pack("{s:s, s:o, s:s}",
@@ -1539,6 +1545,10 @@  int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_LIMITS:
 		root = do_list_obj_json(ctx, cmd, NFT_OBJECT_LIMIT);
 		break;
+	case CMD_OBJ_SECMARK:
+	case CMD_OBJ_SECMARKS:
+		root = do_list_obj_json(ctx, cmd, NFT_OBJECT_SECMARK);
+		break;
 	case CMD_OBJ_FLOWTABLES:
 		root = do_list_flowtables_json(ctx, cmd);
 		break;
diff --git a/src/meta.c b/src/meta.c
index ff0cb12..19066a1 100644
--- a/src/meta.c
+++ b/src/meta.c
@@ -458,6 +458,7 @@  static bool meta_key_is_qualified(enum nft_meta_keys key)
 	case NFT_META_SECPATH:
 	case NFT_META_BRI_IIFNAME:
 	case NFT_META_BRI_OIFNAME:
+	case NFT_META_SECMARK:
 		return true;
 	default:
 		return false;
diff --git a/src/netlink.c b/src/netlink.c
index 864947b..8528011 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -325,6 +325,10 @@  alloc_nftnl_obj(const struct handle *h, struct obj *obj)
 		nftnl_obj_set_u32(nlo, NFTNL_OBJ_QUOTA_FLAGS,
 				  obj->quota.flags);
 		break;
+	case NFT_OBJECT_SECMARK:
+		nftnl_obj_set_str(nlo, NFTNL_OBJ_SECMARK_TEXT,
+				  obj->secmark.ctx);
+		break;
 	case NFT_OBJECT_CT_HELPER:
 		nftnl_obj_set_str(nlo, NFTNL_OBJ_CT_HELPER_NAME,
 				  obj->ct_helper.name);
@@ -1431,6 +1435,10 @@  struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx,
 		obj->quota.flags =
 			nftnl_obj_get_u32(nlo, NFTNL_OBJ_QUOTA_FLAGS);
 		break;
+	case NFT_OBJECT_SECMARK:
+		snprintf(obj->secmark.ctx, sizeof(obj->secmark.ctx), "%s",
+			 nftnl_obj_get_str(nlo, NFTNL_OBJ_SECMARK_TEXT));
+		break;
 	case NFT_OBJECT_CT_HELPER:
 		snprintf(obj->ct_helper.name, sizeof(obj->ct_helper.name), "%s",
 			 nftnl_obj_get_str(nlo, NFTNL_OBJ_CT_HELPER_NAME));
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 31d6242..3402110 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -171,7 +171,7 @@  static void netlink_parse_immediate(struct netlink_parse_ctx *ctx,
 	struct expr *expr;
 
 	if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_VERDICT)) {
-		nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT); 
+		nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT);
 		if  (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) {
 			nld.chain = nftnl_expr_get(nle, NFTNL_EXPR_IMM_CHAIN,
 						   &nld.len);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 33915ed..a42bc05 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -148,6 +148,7 @@  int nft_lex(void *, void *, void *);
 	struct flowtable	*flowtable;
 	struct counter		*counter;
 	struct quota		*quota;
+	struct secmark		*secmark;
 	struct ct		*ct;
 	struct limit		*limit;
 	const struct datatype	*datatype;
@@ -449,6 +450,9 @@  int nft_lex(void *, void *, void *);
 %token QUOTA			"quota"
 %token USED			"used"
 
+%token SECMARK			"secmark"
+%token SECMARKS			"secmarks"
+
 %token NANOSECOND		"nanosecond"
 %token MICROSECOND		"microsecond"
 %token MILLISECOND		"millisecond"
@@ -545,7 +549,7 @@  int nft_lex(void *, void *, void *);
 %type <flowtable>		flowtable_block_alloc flowtable_block
 %destructor { flowtable_free($$); }	flowtable_block_alloc
 
-%type <obj>			obj_block_alloc counter_block quota_block ct_helper_block limit_block
+%type <obj>			obj_block_alloc counter_block quota_block ct_helper_block limit_block secmark_block
 %destructor { obj_free($$); }	obj_block_alloc
 
 %type <list>			stmt_list
@@ -647,8 +651,8 @@  int nft_lex(void *, void *, void *);
 %type <expr>			and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr
 %destructor { expr_free($$); }	and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr
 
-%type <obj>			counter_obj quota_obj ct_obj_alloc limit_obj
-%destructor { obj_free($$); }	counter_obj quota_obj ct_obj_alloc limit_obj
+%type <obj>			counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj
+%destructor { obj_free($$); }	counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj
 
 %type <expr>			relational_expr
 %destructor { expr_free($$); }	relational_expr
@@ -726,6 +730,8 @@  int nft_lex(void *, void *, void *);
 %destructor { xfree($$); }	quota_config
 %type <limit>			limit_config
 %destructor { xfree($$); }	limit_config
+%type <secmark>			secmark_config
+%destructor { xfree($$); }	secmark_config
 
 %type <expr>			tcp_hdr_expr
 %destructor { expr_free($$); }	tcp_hdr_expr
@@ -953,6 +959,10 @@  add_cmd			:	TABLE		table_spec
 			{
 				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_LIMIT, &$2, &@$, $3);
 			}
+			|	SECMARK		obj_spec	secmark_obj
+			{
+				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SECMARK, &$2, &@$, $3);
+			}
 			;
 
 replace_cmd		:	RULE		ruleid_spec	rule
@@ -1034,6 +1044,10 @@  create_cmd		:	TABLE		table_spec
 			{
 				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_LIMIT, &$2, &@$, $3);
 			}
+			|	SECMARK		obj_spec	secmark_obj
+			{
+				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SECMARK, &$2, &@$, $3);
+			}
 			;
 
 insert_cmd		:	RULE		rule_position	rule
@@ -1110,6 +1124,14 @@  delete_cmd		:	TABLE		table_spec
 			{
 				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_LIMIT, &$2, &@$, NULL);
 			}
+			|	SECMARK		obj_spec
+			{
+				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL);
+			}
+			| 	SECMARK		objid_spec
+			{
+				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL);
+			}
 			;
 
 get_cmd			:	ELEMENT		set_spec	set_block_expr
@@ -1182,6 +1204,18 @@  list_cmd		:	TABLE		table_spec
 			{
 				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMIT, &$2, &@$, NULL);
 			}
+			|	SECMARKS	ruleset_spec
+			{
+				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$2, &@$, NULL);
+			}
+			|	SECMARKS	TABLE	table_spec
+			{
+				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$3, &@$, NULL);
+			}
+			|	SECMARK		obj_spec
+			{
+				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARK, &$2, &@$, NULL);
+			}
 			|	RULESET		ruleset_spec
 			{
 				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_RULESET, &$2, &@$, NULL);
@@ -1464,6 +1498,17 @@  table_block		:	/* empty */	{ $$ = $<table>-1; }
 				list_add_tail(&$4->list, &$1->objs);
 				$$ = $1;
 			}
+			|	table_block	SECMARK		obj_identifier
+					obj_block_alloc	'{'	secmark_block	'}'
+					stmt_separator
+			{
+				$4->location = @3;
+				$4->type = NFT_OBJECT_SECMARK;
+				handle_merge(&$4->handle, &$3);
+				handle_free(&$3);
+				list_add_tail(&$4->list, &$1->objs);
+				$$ = $1;
+			}
 			;
 
 chain_block_alloc	:	/* empty */
@@ -1595,6 +1640,15 @@  map_block		:	/* empty */	{ $$ = $<set>-1; }
 				$1->flags  |= NFT_SET_OBJECT;
 				$$ = $1;
 			}
+			|	map_block	TYPE
+						data_type_expr	COLON	SECMARK
+						stmt_separator
+			{
+				$1->key = $3;
+				$1->objtype = NFT_OBJECT_SECMARK;
+				$1->flags  |= NFT_SET_OBJECT;
+				$$ = $1;
+			}
 			|	map_block	FLAGS		set_flag_list	stmt_separator
 			{
 				$1->flags |= $3;
@@ -1757,6 +1811,16 @@  limit_block		:	/* empty */	{ $$ = $<obj>-1; }
 			}
 			;
 
+secmark_block		:	/* empty */	{ $$ = $<obj>-1; }
+			|       secmark_block     common_block
+			|       secmark_block     stmt_separator
+			|       secmark_block     secmark_config
+			{
+				$1->secmark = *$2;
+				$$ = $1;
+			}
+			;
+
 type_identifier		:	STRING	{ $$ = $1; }
 			|	MARK	{ $$ = xstrdup("mark"); }
 			|	DSCP	{ $$ = xstrdup("dscp"); }
@@ -3168,6 +3232,28 @@  quota_obj		:	quota_config
 			}
 			;
 
+secmark_config		:	string
+			{
+				int ret;
+				struct secmark *secmark;
+				secmark = xzalloc(sizeof(*secmark));
+				ret = snprintf(secmark->ctx, sizeof(secmark->ctx), "%s", $1);
+				if (ret <= 0 || ret >= (int)sizeof(secmark->ctx)) {
+					erec_queue(error(&@1, "invalid context '%s', max length is %u\n", $1, (int)sizeof(secmark->ctx)), state->msgs);
+					YYERROR;
+				}
+				$$ = secmark;
+			}
+			;
+
+secmark_obj		:	secmark_config
+			{
+				$$ = obj_alloc(&@$);
+				$$->type = NFT_OBJECT_SECMARK;
+				$$->secmark = *$1;
+			}
+			;
+
 ct_obj_type		:	HELPER		{ $$ = NFT_OBJECT_CT_HELPER; }
 			;
 
@@ -3510,6 +3596,7 @@  meta_key_qualified	:	LENGTH		{ $$ = NFT_META_LEN; }
 			|	PROTOCOL	{ $$ = NFT_META_PROTOCOL; }
 			|	PRIORITY	{ $$ = NFT_META_PRIORITY; }
 			|	RANDOM		{ $$ = NFT_META_PRANDOM; }
+			|	SECMARK		{ $$ = NFT_META_SECMARK; }
 			;
 
 meta_key_unqualified	:	MARK		{ $$ = NFT_META_MARK; }
@@ -3536,7 +3623,16 @@  meta_key_unqualified	:	MARK		{ $$ = NFT_META_MARK; }
 
 meta_stmt		:	META	meta_key	SET	stmt_expr
 			{
-				$$ = meta_stmt_alloc(&@$, $2, $4);
+				switch ($2) {
+				case NFT_META_SECMARK:
+					$$ = objref_stmt_alloc(&@$);
+					$$->objref.type = NFT_OBJECT_SECMARK;
+					$$->objref.expr = $4;
+					break;
+				default:
+					$$ = meta_stmt_alloc(&@$, $2, $4);
+					break;
+				}
 			}
 			|	meta_key_unqualified	SET	stmt_expr
 			{
diff --git a/src/parser_json.c b/src/parser_json.c
index 6e14fb7..1301c3f 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -2260,6 +2260,7 @@  static int string_to_nft_object(const char *str)
 		[NFT_OBJECT_QUOTA] = "quota",
 		[NFT_OBJECT_CT_HELPER] = "ct helper",
 		[NFT_OBJECT_LIMIT] = "limit",
+		[NFT_OBJECT_SECMARK] = "secmark",
 	};
 	unsigned int i;
 
@@ -2584,6 +2585,19 @@  static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
 		if (obj->quota.flags)
 			obj->quota.flags = NFT_QUOTA_F_INV;
 		break;
+	case CMD_OBJ_SECMARK:
+		obj->type = NFT_OBJECT_SECMARK;
+		if (!json_unpack(root, "{s:s}", "context", tmp)) {
+			int ret;
+			ret = snprintf(obj->secmark.ctx, sizeof(obj->secmark.ctx), "%s", tmp);
+			if (ret < 0 || ret >= (int)sizeof(obj->secmark.ctx)) {
+				json_error(ctx, "Invalid secmark context '%s', max length is %zu.",
+					   tmp, sizeof(obj->secmark.ctx));
+				obj_free(obj);
+				return NULL;
+			}
+		}
+		break;
 	case NFT_OBJECT_CT_HELPER:
 		cmd_obj = CMD_OBJ_CT_HELPER;
 		obj->type = NFT_OBJECT_CT_HELPER;
@@ -2675,7 +2689,8 @@  static struct cmd *json_parse_cmd_add(struct json_ctx *ctx,
 		{ "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object },
 		{ "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object },
 		{ "ct helper", NFT_OBJECT_CT_HELPER, json_parse_cmd_add_object },
-		{ "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object }
+		{ "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object },
+		{ "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object }
 	};
 	unsigned int i;
 	json_t *tmp;
@@ -2841,6 +2856,8 @@  static struct cmd *json_parse_cmd_list(struct json_ctx *ctx,
 		{ "meter", CMD_OBJ_METER, json_parse_cmd_add_set },
 		{ "meters", CMD_OBJ_METERS, json_parse_cmd_list_multiple },
 		{ "flowtables", CMD_OBJ_FLOWTABLES, json_parse_cmd_list_multiple },
+		{ "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object },
+		{ "secmarks", CMD_OBJ_SECMARKS, json_parse_cmd_list_multiple },
 	};
 	unsigned int i;
 	json_t *tmp;
diff --git a/src/rule.c b/src/rule.c
index 56b956a..0431cf2 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -1090,6 +1090,7 @@  void cmd_free(struct cmd *cmd)
 		case CMD_OBJ_QUOTA:
 		case CMD_OBJ_CT_HELPER:
 		case CMD_OBJ_LIMIT:
+		case CMD_OBJ_SECMARK:
 			obj_free(cmd->object);
 			break;
 		case CMD_OBJ_FLOWTABLE:
@@ -1184,6 +1185,7 @@  static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl)
 	case CMD_OBJ_QUOTA:
 	case CMD_OBJ_CT_HELPER:
 	case CMD_OBJ_LIMIT:
+	case CMD_OBJ_SECMARK:
 		return netlink_add_obj(ctx, cmd, flags);
 	case CMD_OBJ_FLOWTABLE:
 		return netlink_add_flowtable(ctx, cmd, flags);
@@ -1270,6 +1272,8 @@  static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd)
 		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
 	case CMD_OBJ_LIMIT:
 		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_LIMIT);
+	case CMD_OBJ_SECMARK:
+		return netlink_delete_obj(ctx, cmd, NFT_OBJECT_SECMARK);
 	case CMD_OBJ_FLOWTABLE:
 		return netlink_delete_flowtable(ctx, cmd);
 	default:
@@ -1454,6 +1458,13 @@  static void obj_print_data(const struct obj *obj,
 		}
 		}
 		break;
+	case NFT_OBJECT_SECMARK:
+		nft_print(octx, " %s {", obj->handle.obj.name);
+		if (octx->handle > 0)
+			nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id);
+		nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab);
+		nft_print(octx, "%s", obj->secmark.ctx);
+		break;
 	case NFT_OBJECT_CT_HELPER:
 		nft_print(octx, "ct helper %s {", obj->handle.obj.name);
 		if (octx->handle > 0)
@@ -1511,6 +1522,7 @@  static const char * const obj_type_name_array[] = {
 	[NFT_OBJECT_QUOTA]	= "quota",
 	[NFT_OBJECT_CT_HELPER]	= "",
 	[NFT_OBJECT_LIMIT]	= "limit",
+	[NFT_OBJECT_SECMARK]	= "secmark",
 };
 
 const char *obj_type_name(enum stmt_types type)
@@ -1525,6 +1537,7 @@  static uint32_t obj_type_cmd_array[NFT_OBJECT_MAX + 1] = {
 	[NFT_OBJECT_QUOTA]	= CMD_OBJ_QUOTA,
 	[NFT_OBJECT_CT_HELPER]	= CMD_OBJ_CT_HELPER,
 	[NFT_OBJECT_LIMIT]	= CMD_OBJ_LIMIT,
+	[NFT_OBJECT_SECMARK]	= CMD_OBJ_SECMARK,
 };
 
 uint32_t obj_type_to_cmd(uint32_t type)
@@ -1878,6 +1891,9 @@  static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd)
 	case CMD_OBJ_LIMIT:
 	case CMD_OBJ_LIMITS:
 		return do_list_obj(ctx, cmd, NFT_OBJECT_LIMIT);
+	case CMD_OBJ_SECMARK:
+	case CMD_OBJ_SECMARKS:
+		return do_list_obj(ctx, cmd, NFT_OBJECT_SECMARK);
 	case CMD_OBJ_FLOWTABLES:
 		return do_list_flowtables(ctx, cmd);
 	default:
diff --git a/src/scanner.l b/src/scanner.l
index 416bd27..9a5bd30 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -547,6 +547,9 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 
 "exthdr"		{ return EXTHDR; }
 
+"secmark"		{ return SECMARK; }
+"secmarks"		{ return SECMARKS; }
+
 {addrstring}		{
 				yylval->string = xstrdup(yytext);
 				return STRING;
diff --git a/src/statement.c b/src/statement.c
index 58e86f2..ebdcb50 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -202,6 +202,7 @@  static const char *objref_type[NFT_OBJECT_MAX + 1] = {
 	[NFT_OBJECT_QUOTA]	= "quota",
 	[NFT_OBJECT_CT_HELPER]	= "ct helper",
 	[NFT_OBJECT_LIMIT]	= "limit",
+	[NFT_OBJECT_SECMARK]	= "secmark",
 };
 
 const char *objref_type_name(uint32_t type)