]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xdrgen: Add enum value validation to generated decoders
authorChuck Lever <chuck.lever@oracle.com>
Fri, 26 Dec 2025 15:19:35 +0000 (10:19 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 26 Jan 2026 15:10:58 +0000 (10:10 -0500)
XDR enum decoders generated by xdrgen do not verify that incoming
values are valid members of the enum. Incoming out-of-range values
from malicious or buggy peers propagate through the system
unchecked.

Add validation logic to generated enum decoders using a switch
statement that explicitly lists valid enumerator values. The
compiler optimizes this to a simple range check when enum values
are dense (contiguous), while correctly rejecting invalid values
for sparse enums with gaps in their value ranges.

The --no-enum-validation option on the source subcommand disables
this validation when not needed.

The minimum and maximum fields in _XdrEnum, which were previously
unused placeholders for a range-based validation approach, have
been removed since the switch-based validation handles both dense
and sparse enums correctly.

Because the new mechanism results in substantive changes to
generated code, existing .x files are regenerated. Unrelated white
space and semicolon changes in the generated code are due to recent
commit 1c873a2fd110 ("xdrgen: Don't generate unnecessary semicolon")
and commit 38c4df91242b ("xdrgen: Address some checkpatch whitespace
complaints").

Reviewed-by: NeilBrown <neil@brown.name>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/nfs4xdr_gen.c
fs/nfsd/nfs4xdr_gen.h
include/linux/sunrpc/xdrgen/nfs4_1.h
tools/net/sunrpc/xdrgen/generators/enum.py
tools/net/sunrpc/xdrgen/subcmds/source.py
tools/net/sunrpc/xdrgen/templates/C/enum/decoder/enum.j2
tools/net/sunrpc/xdrgen/templates/C/enum/decoder/enum_be.j2
tools/net/sunrpc/xdrgen/xdr_ast.py
tools/net/sunrpc/xdrgen/xdr_parse.py
tools/net/sunrpc/xdrgen/xdrgen

index a17b5d8e60b3579caa2e2a8b40ed757070e1a622..1e5e2243625c8d35810affc7f77cc0f21ef677c3 100644 (file)
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 // Generated by xdrgen. Manual edits will be lost.
 // XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x
-// XDR specification modification time: Mon Oct 14 09:10:13 2024
+// XDR specification modification time: Thu Dec 25 13:44:43 2025
 
 #include <linux/sunrpc/svc.h>
 
@@ -11,13 +11,13 @@ static bool __maybe_unused
 xdrgen_decode_int64_t(struct xdr_stream *xdr, int64_t *ptr)
 {
        return xdrgen_decode_hyper(xdr, ptr);
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_uint32_t(struct xdr_stream *xdr, uint32_t *ptr)
 {
        return xdrgen_decode_unsigned_int(xdr, ptr);
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr)
@@ -28,7 +28,7 @@ xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr)
                if (!xdrgen_decode_uint32_t(xdr, &ptr->element[i]))
                        return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr)
@@ -38,13 +38,13 @@ xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr)
        if (!xdrgen_decode_uint32_t(xdr, &ptr->nseconds))
                return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_fattr4_offline(struct xdr_stream *xdr, fattr4_offline *ptr)
 {
        return xdrgen_decode_bool(xdr, ptr);
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_open_arguments4(struct xdr_stream *xdr, struct open_arguments4 *ptr)
@@ -60,7 +60,7 @@ xdrgen_decode_open_arguments4(struct xdr_stream *xdr, struct open_arguments4 *pt
        if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_create_mode))
                return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 *ptr)
@@ -69,6 +69,15 @@ xdrgen_decode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_ac
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_ARGS_SHARE_ACCESS_READ:
+       case OPEN_ARGS_SHARE_ACCESS_WRITE:
+       case OPEN_ARGS_SHARE_ACCESS_BOTH:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -80,6 +89,16 @@ xdrgen_decode_open_args_share_deny4(struct xdr_stream *xdr, open_args_share_deny
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_ARGS_SHARE_DENY_NONE:
+       case OPEN_ARGS_SHARE_DENY_READ:
+       case OPEN_ARGS_SHARE_DENY_WRITE:
+       case OPEN_ARGS_SHARE_DENY_BOTH:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -91,6 +110,19 @@ xdrgen_decode_open_args_share_access_want4(struct xdr_stream *xdr, open_args_sha
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_ARGS_SHARE_ACCESS_WANT_ANY_DELEG:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_NO_DELEG:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_CANCEL:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS:
+       case OPEN_ARGS_SHARE_ACCESS_WANT_OPEN_XOR_DELEGATION:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -102,6 +134,19 @@ xdrgen_decode_open_args_open_claim4(struct xdr_stream *xdr, open_args_open_claim
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_ARGS_OPEN_CLAIM_NULL:
+       case OPEN_ARGS_OPEN_CLAIM_PREVIOUS:
+       case OPEN_ARGS_OPEN_CLAIM_DELEGATE_CUR:
+       case OPEN_ARGS_OPEN_CLAIM_DELEGATE_PREV:
+       case OPEN_ARGS_OPEN_CLAIM_FH:
+       case OPEN_ARGS_OPEN_CLAIM_DELEG_CUR_FH:
+       case OPEN_ARGS_OPEN_CLAIM_DELEG_PREV_FH:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -113,6 +158,16 @@ xdrgen_decode_open_args_createmode4(struct xdr_stream *xdr, open_args_createmode
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_ARGS_CREATEMODE_UNCHECKED4:
+       case OPEN_ARGS_CREATE_MODE_GUARDED:
+       case OPEN_ARGS_CREATEMODE_EXCLUSIVE4:
+       case OPEN_ARGS_CREATE_MODE_EXCLUSIVE4_1:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -121,19 +176,19 @@ bool
 xdrgen_decode_fattr4_open_arguments(struct xdr_stream *xdr, fattr4_open_arguments *ptr)
 {
        return xdrgen_decode_open_arguments4(xdr, ptr);
-};
+}
 
 bool
 xdrgen_decode_fattr4_time_deleg_access(struct xdr_stream *xdr, fattr4_time_deleg_access *ptr)
 {
        return xdrgen_decode_nfstime4(xdr, ptr);
-};
+}
 
 bool
 xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg_modify *ptr)
 {
        return xdrgen_decode_nfstime4(xdr, ptr);
-};
+}
 
 static bool __maybe_unused
 xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 *ptr)
@@ -142,6 +197,18 @@ xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+       case OPEN_DELEGATE_NONE:
+       case OPEN_DELEGATE_READ:
+       case OPEN_DELEGATE_WRITE:
+       case OPEN_DELEGATE_NONE_EXT:
+       case OPEN_DELEGATE_READ_ATTRS_DELEG:
+       case OPEN_DELEGATE_WRITE_ATTRS_DELEG:
+               break;
+       default:
+               return false;
+       }
        *ptr = val;
        return true;
 }
@@ -150,13 +217,13 @@ static bool __maybe_unused
 xdrgen_encode_int64_t(struct xdr_stream *xdr, const int64_t value)
 {
        return xdrgen_encode_hyper(xdr, value);
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_uint32_t(struct xdr_stream *xdr, const uint32_t value)
 {
        return xdrgen_encode_unsigned_int(xdr, value);
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value)
@@ -167,7 +234,7 @@ xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value)
                if (!xdrgen_encode_uint32_t(xdr, value.element[i]))
                        return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value)
@@ -177,13 +244,13 @@ xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value)
        if (!xdrgen_encode_uint32_t(xdr, value->nseconds))
                return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_fattr4_offline(struct xdr_stream *xdr, const fattr4_offline value)
 {
        return xdrgen_encode_bool(xdr, value);
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_open_arguments4(struct xdr_stream *xdr, const struct open_arguments4 *value)
@@ -199,7 +266,7 @@ xdrgen_encode_open_arguments4(struct xdr_stream *xdr, const struct open_argument
        if (!xdrgen_encode_bitmap4(xdr, value->oa_create_mode))
                return false;
        return true;
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 value)
@@ -235,19 +302,19 @@ bool
 xdrgen_encode_fattr4_open_arguments(struct xdr_stream *xdr, const fattr4_open_arguments *value)
 {
        return xdrgen_encode_open_arguments4(xdr, value);
-};
+}
 
 bool
 xdrgen_encode_fattr4_time_deleg_access(struct xdr_stream *xdr, const fattr4_time_deleg_access *value)
 {
        return xdrgen_encode_nfstime4(xdr, value);
-};
+}
 
 bool
 xdrgen_encode_fattr4_time_deleg_modify(struct xdr_stream *xdr, const fattr4_time_deleg_modify *value)
 {
        return xdrgen_encode_nfstime4(xdr, value);
-};
+}
 
 static bool __maybe_unused
 xdrgen_encode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 value)
index 41a0033b72562ee3c1fcdcd4a887ce635385b22b..47437876e8030fdd8d489c265e86633b1e2e7f75 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /* Generated by xdrgen. Manual edits will be lost. */
 /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
-/* XDR specification modification time: Mon Oct 14 09:10:13 2024 */
+/* XDR specification modification time: Thu Dec 25 13:44:43 2025 */
 
 #ifndef _LINUX_XDRGEN_NFS4_1_DECL_H
 #define _LINUX_XDRGEN_NFS4_1_DECL_H
index cf21a14aa8850f4b21cd365cb7bc22a02c6097ce..352bffda08f7457f8168b6dbd13637fd00635db3 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /* Generated by xdrgen. Manual edits will be lost. */
 /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
-/* XDR specification modification time: Mon Oct 14 09:10:13 2024 */
+/* XDR specification modification time: Thu Dec 25 13:44:43 2025 */
 
 #ifndef _LINUX_XDRGEN_NFS4_1_DEF_H
 #define _LINUX_XDRGEN_NFS4_1_DEF_H
@@ -40,6 +40,7 @@ enum open_args_share_access4 {
        OPEN_ARGS_SHARE_ACCESS_WRITE = 2,
        OPEN_ARGS_SHARE_ACCESS_BOTH = 3,
 };
+
 typedef enum open_args_share_access4 open_args_share_access4;
 
 enum open_args_share_deny4 {
@@ -48,6 +49,7 @@ enum open_args_share_deny4 {
        OPEN_ARGS_SHARE_DENY_WRITE = 2,
        OPEN_ARGS_SHARE_DENY_BOTH = 3,
 };
+
 typedef enum open_args_share_deny4 open_args_share_deny4;
 
 enum open_args_share_access_want4 {
@@ -59,6 +61,7 @@ enum open_args_share_access_want4 {
        OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS = 20,
        OPEN_ARGS_SHARE_ACCESS_WANT_OPEN_XOR_DELEGATION = 21,
 };
+
 typedef enum open_args_share_access_want4 open_args_share_access_want4;
 
 enum open_args_open_claim4 {
@@ -70,6 +73,7 @@ enum open_args_open_claim4 {
        OPEN_ARGS_OPEN_CLAIM_DELEG_CUR_FH = 5,
        OPEN_ARGS_OPEN_CLAIM_DELEG_PREV_FH = 6,
 };
+
 typedef enum open_args_open_claim4 open_args_open_claim4;
 
 enum open_args_createmode4 {
@@ -78,6 +82,7 @@ enum open_args_createmode4 {
        OPEN_ARGS_CREATEMODE_EXCLUSIVE4 = 2,
        OPEN_ARGS_CREATE_MODE_EXCLUSIVE4_1 = 3,
 };
+
 typedef enum open_args_createmode4 open_args_createmode4;
 
 typedef struct open_arguments4 fattr4_open_arguments;
@@ -124,6 +129,7 @@ enum open_delegation_type4 {
        OPEN_DELEGATE_READ_ATTRS_DELEG = 4,
        OPEN_DELEGATE_WRITE_ATTRS_DELEG = 5,
 };
+
 typedef enum open_delegation_type4 open_delegation_type4;
 
 #define NFS4_int64_t_sz                 \
index e62f715d3996f3407460603aa4eb19b6ec91c715..b4ed3ed6431e9e0cb713a2753da6c0ef9c68ec6f 100644 (file)
@@ -5,6 +5,7 @@
 
 from generators import SourceGenerator, create_jinja2_environment
 from xdr_ast import _XdrEnum, public_apis, big_endian, get_header_name
+from xdr_parse import get_xdr_enum_validation
 
 
 class XdrEnumGenerator(SourceGenerator):
@@ -42,7 +43,13 @@ class XdrEnumGenerator(SourceGenerator):
             template = self.environment.get_template("decoder/enum_be.j2")
         else:
             template = self.environment.get_template("decoder/enum.j2")
-        print(template.render(name=node.name))
+        print(
+            template.render(
+                name=node.name,
+                enumerators=node.enumerators,
+                validate=get_xdr_enum_validation(),
+            )
+        )
 
     def emit_encoder(self, node: _XdrEnum) -> None:
         """Emit one encoder function for an XDR enum type"""
index 08c883f547d7c18844b19dad38346e1c8a3e4eb4..6508563494fecd376fee835d48918ea7c1576f19 100644 (file)
@@ -22,7 +22,7 @@ from xdr_ast import transform_parse_tree, _RpcProgram, Specification
 from xdr_ast import _XdrAst, _XdrEnum, _XdrPointer
 from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion
 
-from xdr_parse import xdr_parser, set_xdr_annotate
+from xdr_parse import xdr_parser, set_xdr_annotate, set_xdr_enum_validation
 from xdr_parse import make_error_handler, XdrParseError
 from xdr_parse import handle_transform_error
 
@@ -98,6 +98,7 @@ def subcmd(args: Namespace) -> int:
     """Generate encoder and decoder functions"""
 
     set_xdr_annotate(args.annotate)
+    set_xdr_enum_validation(not args.no_enum_validation)
     parser = xdr_parser()
     with open(args.filename, encoding="utf-8") as f:
         source = f.read()
index 6482984f1cb7157fcdede3802bea86277f8ae82c..735a34157fdf41d299bab9d9b96e1ae3a4b90082 100644 (file)
@@ -14,6 +14,17 @@ xdrgen_decode_{{ name }}(struct xdr_stream *xdr, {{ name }} *ptr)
 
        if (xdr_stream_decode_u32(xdr, &val) < 0)
                return false;
+{% if validate and enumerators %}
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+{% for e in enumerators %}
+       case {{ e.name }}:
+{% endfor %}
+               break;
+       default:
+               return false;
+       }
+{% endif %}
        *ptr = val;
        return true;
 }
index 44c391c10b42ed72e53bfe90659595c8b1fc90ae..82782a510d47414aa3dfd8e6d6e79132e4cceb8d 100644 (file)
@@ -10,5 +10,25 @@ static bool __maybe_unused
 {% endif %}
 xdrgen_decode_{{ name }}(struct xdr_stream *xdr, {{ name }} *ptr)
 {
+{% if validate and enumerators %}
+       __be32 raw;
+       u32 val;
+
+       if (xdr_stream_decode_be32(xdr, &raw) < 0)
+               return false;
+       val = be32_to_cpu(raw);
+       /* Compiler may optimize to a range check for dense enums */
+       switch (val) {
+{% for e in enumerators %}
+       case {{ e.name }}:
+{% endfor %}
+               break;
+       default:
+               return false;
+       }
+       *ptr = raw;
+       return true;
+{% else %}
        return xdr_stream_decode_be32(xdr, ptr) == 0;
+{% endif %}
 }
index 2b5d160a0a60a16bf39c2f4dedf5004ef46a3dd0..dc2fa9fd8ec2d41e100992bf8eae1f562c56e4a1 100644 (file)
@@ -330,8 +330,6 @@ class _XdrEnum(_XdrAst):
     """An XDR enum definition"""
 
     name: str
-    minimum: int
-    maximum: int
     enumerators: List[_XdrEnumerator]
 
     def max_width(self) -> int:
@@ -572,8 +570,6 @@ class ParseToAst(Transformer):
         value = children[1].value
         return _XdrConstant(name, value)
 
-    # cel: Python can compute a min() and max() for the enumerator values
-    #      so that the generated code can perform proper range checking.
     def enum(self, children):
         """Instantiate one _XdrEnum object"""
         enum_name = children[0].symbol
@@ -587,7 +583,7 @@ class ParseToAst(Transformer):
             enumerators.append(_XdrEnumerator(name, value))
             i = i + 2
 
-        return _XdrEnum(enum_name, 0, 0, enumerators)
+        return _XdrEnum(enum_name, enumerators)
 
     def fixed_length_opaque(self, children):
         """Instantiate one _XdrFixedLengthOpaque declaration object"""
index 38724ad5aea26c27fe972a119d6d90ae68ee4a34..241e96c1fdd9c776c14298b132e31d7efe6b1aec 100644 (file)
@@ -13,6 +13,9 @@ from lark.exceptions import UnexpectedInput, UnexpectedToken, VisitError
 # Set to True to emit annotation comments in generated source
 annotate = False
 
+# Set to True to emit enum value validation in decoders
+enum_validation = True
+
 # Map internal Lark token names to human-readable names
 TOKEN_NAMES = {
     "__ANON_0": "identifier",
@@ -49,6 +52,17 @@ def get_xdr_annotate() -> bool:
     return annotate
 
 
+def set_xdr_enum_validation(set_it: bool) -> None:
+    """Set 'enum_validation' based on command line options"""
+    global enum_validation
+    enum_validation = set_it
+
+
+def get_xdr_enum_validation() -> bool:
+    """Return True when enum validation is enabled for decoder generation"""
+    return enum_validation
+
+
 def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput], bool]:
     """Create an error handler that reports the first parse error and aborts.
 
index e22638f8324ba2d7ef02ac9879ec78aa3aefe1c2..b2fb43f4a2ec2380314f8155f3f6cebcbd0789ba 100755 (executable)
@@ -123,6 +123,12 @@ There is NO WARRANTY, to the extent permitted by law.""",
         help="Generate code for client or server side",
         type=str,
     )
+    source_parser.add_argument(
+        "--no-enum-validation",
+        action="store_true",
+        default=False,
+        help="Disable enum value validation in decoders",
+    )
     source_parser.add_argument("filename", help="File containing an XDR specification")
     source_parser.set_defaults(func=source.subcmd)