xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_validate.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
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