]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
tmpl, value, xlat: carry null through arg lists instead of casting it
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 22 Apr 2026 18:32:48 +0000 (14:32 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 23 Apr 2026 17:58:16 +0000 (13:58 -0400)
Reverses the cast coercion added in 3b5165084f.  An explicit `null`
should not silently become "" or zero-length octets - callers that
wrote `null` meant "no value at all", which is a different shape
from "the empty string".

value.c: fr_value_box_cast_to_{string,octets} now return a clean
fr_strerror() on FR_TYPE_NULL source instead of falling through to
the catch-all fr_assert(0).

xlat_tokenize.c: xlat_validate_function_arg skips the compile-time
cast for FR_TYPE_NULL literals so a bareword `null` survives arg
validation.

xlat_eval.c: the runtime concat and per-box cast paths both pass an
FR_TYPE_NULL source through to the xlat body unchanged, so
implementations can check fr_type_is_null() on the incoming box
and react accordingly.

src/lib/unlang/xlat_eval.c
src/lib/unlang/xlat_tokenize.c
src/lib/util/value.c

index d9e8fec900494af9feb0c84490c609b1dac8c9a6..86ff38830b8a3bc4f3e470d88bd90b66f6fee0b9 100644 (file)
@@ -462,8 +462,12 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
 
        /*
         *      Concatenate child boxes, then cast to the desired type.
+        *      Skip for an explicit `null` - concatenating would force
+        *      a cast of FR_TYPE_NULL to the arg's declared type, which
+        *      would either error or silently coerce to a zero-length
+        *      value.  Let the null box reach check_types intact.
         */
-       if (concat) {
+       if (concat && !(fr_value_box_list_num_elements(list) == 1 && fr_type_is_null(vb->type))) {
                if (fr_value_box_list_concat_in_place(ctx, vb, list, type, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
                        RPEDEBUG("Function \"%s\" failed concatenating arguments to type %s", name, fr_type_to_str(type));
                        return XLAT_ACTION_FAIL;
@@ -473,6 +477,8 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
                goto check_types;
        }
 
+       if (concat) goto check_types;
+
        /*
         *      Only a single child box is valid here.  Check there is
         *      just one, cast to the correct type
@@ -490,6 +496,16 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
        check_types:
                if (!fr_type_is_leaf(arg->type)) goto check_non_leaf;
 
+               /*
+                *      FR_TYPE_NULL is an explicit "no value" placeholder
+                *      (the `null` keyword).  Passing it through to the
+                *      xlat body lets the implementation distinguish it
+                *      from a zero-length value of the declared type; the
+                *      author opted-in by writing `null` in the source.
+                *      Casting it would paper over the distinction.
+                */
+               if (fr_type_is_null(vb->type)) return XLAT_ACTION_DONE;
+
                /*
                 *      Cast to the correct type if necessary.
                 */
index 0bd3e04b91c61c4d5b25b47fc997cba050ac34bd..4ed9b39b33371a5767c69bfb3d3991fa074898f1 100644 (file)
@@ -340,6 +340,14 @@ static int xlat_validate_function_arg(xlat_arg_parser_t const *arg_p, xlat_exp_t
                return 0;
        }
 
+       /*
+        *      An explicit `null` literal is preserved unchanged - the xlat
+        *      body receives an FR_TYPE_NULL box in this arg slot and can
+        *      decide what to do with it.  Casting would collapse it into a
+        *      zero-length value of the declared type and hide the intent.
+        */
+       if (fr_type_is_null(node->data.type)) return 0;
+
        /*
         *      Cast (or parse) the input data to the expected argument data type.
         */
index 331c1caf2e11e15b753688e3281d27c7f05644cf..234630126f3ccc22c8335de78a5ad185c3f85c09 100644 (file)
@@ -2616,10 +2616,12 @@ static inline int fr_value_box_cast_to_strvalue(TALLOC_CTX *ctx, fr_value_box_t
 
        switch (src->type) {
        /*
-        *      Explicit null casts to an empty string.
+        *      An explicit `null` has no representation to cast from.
+        *      Refuse rather than silently coerce to an empty string.
         */
        case FR_TYPE_NULL:
-               return fr_value_box_bstrndup(ctx, dst, dst_enumv, "", 0, src->tainted);
+               fr_strerror_const("Cannot cast null to a string");
+               return -1;
 
        /*
         *      The presentation format of octets is hex
@@ -2674,11 +2676,12 @@ static inline int fr_value_box_cast_to_octets(TALLOC_CTX *ctx, fr_value_box_t *d
 
        switch (src->type) {
        /*
-        *      An explicit null (e.g. the `null` keyword in unlang)
-        *      casts to a zero-length octets box.
+        *      An explicit `null` has no representation to cast from.
+        *      Refuse rather than silently coerce to zero-length octets.
         */
        case FR_TYPE_NULL:
-               return fr_value_box_memdup(ctx, dst, dst_enumv, NULL, 0, src->tainted);
+               fr_strerror_const("Cannot cast null to octets");
+               return -1;
 
        /*
         *      <string> (excluding terminating \0)