1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright (c) 2026 by Garth Snyder. All rights reserved.
15 */
16
17 #include <assert.h>
18 #include <err.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/dmu_recv.h>
22 #include <sys/stdtypes.h>
23 #include <sys/zfs_ioctl.h>
24
25 #include "zstream_modules.h"
26
27 /*
28 * Validate consistency and well-formedness of the actual DRR records.
29 */
30
31 #define MAX_VALIDATIONS 4
32
33 typedef struct {
34 int nesting;
35 uint64_t featureflags;
36 boolean_t begin_spill;
37 } validate_context_t;
38
39 static validate_context_t contexts[MAX_VALIDATIONS];
40 static int next_context = 0;
41
42 static void
validate_fail(int err,const char * msg)43 validate_fail(int err, const char *msg)
44 {
45 if (err == 0)
46 return;
47 if (msg != NULL && msg[0] != '\0')
48 errx(1, "%s", msg);
49 errx(1, "invalid receive stream record (error %d)", err);
50 }
51
52 static boolean_t
validate_stream_has_feature(const validate_context_t * context,uint64_t feature)53 validate_stream_has_feature(const validate_context_t *context, uint64_t feature)
54 {
55 /*
56 * STREAM_HAS_FEATURE() describes the first DRR_BEGIN in the input.
57 * Recursive streams can contain later BEGIN records with different
58 * feature flags, so validation must use the current substream flags.
59 */
60 return ((context->featureflags & feature) != 0);
61 }
62
63 static disposition_t
chain_validate_records(void * item_in,void * context_in)64 chain_validate_records(void *item_in, void *context_in)
65 {
66 drr_packet_t *item = (drr_packet_t *)item_in;
67 validate_context_t *context = (validate_context_t *)context_in;
68
69 if (item == NULL)
70 return (D_OK);
71
72 struct dmu_replay_record *drr = &item->dp_drr;
73 struct drr_write *drrw = &drr->drr_u.drr_write;
74 struct drr_object *drro = &drr->drr_u.drr_object;
75 struct drr_spill *drrs = &drr->drr_u.drr_spill;
76 struct drr_write_embedded *drrwe = &drr->drr_u.drr_write_embedded;
77 struct drr_free *drrf = &drr->drr_u.drr_free;
78 struct drr_freeobjects *drrfo = &drr->drr_u.drr_freeobjects;
79 struct drr_object_range *drror = &drr->drr_u.drr_object_range;
80 char errbuf[RECV_CHECK_ERRBUFLEN];
81 int err;
82 boolean_t is_raw;
83
84 if (OPTION_ENABLED(CA_DO_NOT_VALIDATE))
85 return (D_OK);
86
87 if (item->dp_stream_offset == 0 && drr->drr_type != DRR_BEGIN) {
88 warnx("warning - first record is not DRR_BEGIN");
89 }
90
91 if (drr->drr_type == DRR_BEGIN) {
92 VERIFY0(context->nesting);
93 context->nesting++;
94 context->featureflags = DMU_GET_FEATUREFLAGS(
95 drr->drr_u.drr_begin.drr_versioninfo);
96 context->begin_spill = !!(drr->drr_u.drr_begin.drr_flags &
97 DRR_FLAG_SPILL_BLOCK);
98 } else if (drr->drr_type == DRR_END) {
99 VERIFY3S(context->nesting, >=, 0);
100 if (context->nesting > 0)
101 context->nesting--;
102 } else if (drr->drr_type >= DRR_NUMTYPES) {
103 errx(1, "unknown record type: %d", drr->drr_type);
104 } else {
105 VERIFY3S(context->nesting, ==, 1);
106 }
107
108 is_raw = validate_stream_has_feature(context, DMU_BACKUP_FEATURE_RAW);
109
110 switch (drr->drr_type) {
111
112 case DRR_BEGIN:
113 VERIFY3U(item->dp_payload_size, <=, 1UL << 28);
114 break;
115
116 case DRR_OBJECT:
117 err = recv_check_drr_object(drro, NULL, is_raw,
118 context->begin_spill, context->featureflags, errbuf,
119 sizeof (errbuf));
120 validate_fail(err, errbuf);
121 break;
122
123 case DRR_WRITE:
124 err = recv_check_drr_write(drrw, NULL, is_raw,
125 context->featureflags, errbuf, sizeof (errbuf));
126 validate_fail(err, errbuf);
127 break;
128
129 case DRR_WRITE_EMBEDDED:
130 err = recv_check_drr_write_embedded(drrwe, NULL, is_raw,
131 context->featureflags, errbuf, sizeof (errbuf));
132 validate_fail(err, errbuf);
133 break;
134
135 case DRR_SPILL:
136 err = recv_check_drr_spill(drrs, NULL, is_raw,
137 context->featureflags, errbuf, sizeof (errbuf));
138 validate_fail(err, errbuf);
139 break;
140
141 case DRR_FREE:
142 case DRR_REDACT:
143 err = recv_check_drr_free(drrf, errbuf, sizeof (errbuf));
144 validate_fail(err, errbuf);
145 break;
146
147 case DRR_FREEOBJECTS:
148 err = recv_check_drr_freeobjects(drrfo, errbuf,
149 sizeof (errbuf));
150 validate_fail(err, errbuf);
151 break;
152
153 case DRR_OBJECT_RANGE:
154 err = recv_check_drr_object_range(drror, is_raw, errbuf,
155 sizeof (errbuf));
156 validate_fail(err, errbuf);
157 break;
158
159 default:
160 break;
161 }
162
163 return (D_OK);
164 }
165
166 chain_step_t
serial_validate_records(void)167 serial_validate_records(void)
168 {
169 int context_ix = next_context++ % MAX_VALIDATIONS;
170 validate_context_t *context = &contexts[context_ix];
171 context->nesting = 0;
172 context->featureflags = 0;
173 context->begin_spill = B_FALSE;
174
175 chain_step_t step = {
176 .cs_type = CS_SERIAL,
177 .cs_in_size = sizeof (drr_packet_t),
178 .cs_out_size = sizeof (drr_packet_t),
179 .cs_context = context,
180 .cs_serial = {
181 .process = chain_validate_records,
182 }
183 };
184 return (step);
185 }
186