Print this page
NEX-4878 CIFS assertion failed on common/fs/smbsrv/smb_sd.c, line: 57
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-1635 Codenomicon: SMB2 TC: 157974 Panic in smb2_create/smb_decode_sd
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/uts/common/fs/smbsrv/smb_nt_transact_security.c
+++ new/usr/src/uts/common/fs/smbsrv/smb_nt_transact_security.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
|
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 - * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
24 + * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
25 25 */
26 26
27 27 #include <smbsrv/smb_kproto.h>
28 28
29 29 static void smb_encode_sacl(mbuf_chain_t *, smb_acl_t *);
30 30 static void smb_encode_dacl(mbuf_chain_t *, smb_acl_t *);
31 31 static smb_acl_t *smb_decode_acl(mbuf_chain_t *, uint32_t);
32 32
33 33 /*
34 34 * smb_nt_transact_query_security_info
35 35 *
36 36 * This command allows the client to retrieve the security descriptor
37 37 * on a file. The result of the call is returned to the client in the
38 38 * Data part of the transaction response.
39 39 *
40 40 * Some clients specify a non-zero maximum data return size (mdrcnt)
41 41 * for the SD and some specify zero. In either case, if the mdrcnt is
42 42 * too small we need to return NT_STATUS_BUFFER_TOO_SMALL and a buffer
43 43 * size hint. The client should then retry with the appropriate buffer
44 44 * size.
45 45 *
46 46 * Client Parameter Block Description
47 47 * ================================== =================================
48 48 *
49 49 * USHORT Fid; FID of target
50 50 * USHORT Reserved; MBZ
51 51 * ULONG secinfo; Fields of descriptor to set
52 52 *
53 53 * Data Block Encoding Description
54 54 * ================================== ==================================
55 55 *
56 56 * Data[TotalDataCount] Security Descriptor information
57 57 */
58 58
59 59 smb_sdrc_t
60 60 smb_nt_transact_query_security_info(struct smb_request *sr, struct smb_xa *xa)
61 61 {
62 62 smb_sd_t sd;
63 63 uint32_t secinfo;
64 64 uint32_t sdlen;
65 65 uint32_t status;
66 66 smb_error_t err;
67 67
68 68 if (smb_mbc_decodef(&xa->req_param_mb, "w2.l",
69 69 &sr->smb_fid, &secinfo) != 0) {
70 70 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0);
71 71 return (SDRC_ERROR);
72 72 }
73 73
74 74 smbsr_lookup_file(sr);
75 75 if (sr->fid_ofile == NULL) {
76 76 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
77 77 return (SDRC_ERROR);
78 78 }
79 79
80 80
81 81 if ((sr->fid_ofile->f_node == NULL) ||
82 82 (sr->fid_ofile->f_ftype != SMB_FTYPE_DISK)) {
83 83 smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
84 84 ERRDOS, ERROR_ACCESS_DENIED);
85 85 return (SDRC_ERROR);
86 86 }
87 87
88 88 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
89 89
90 90 if (sr->tid_tree->t_acltype != ACE_T) {
91 91 /*
92 92 * If target filesystem doesn't support ACE_T acls then
93 93 * don't process SACL
94 94 */
95 95 secinfo &= ~SMB_SACL_SECINFO;
96 96 }
97 97
98 98 status = smb_sd_read(sr, &sd, secinfo);
99 99 if (status != NT_STATUS_SUCCESS) {
100 100 smbsr_error(sr, status, 0, 0);
101 101 return (SDRC_ERROR);
102 102 }
103 103
104 104 sdlen = smb_sd_len(&sd, secinfo);
105 105 if (sdlen == 0) {
106 106 smb_sd_term(&sd);
107 107 smbsr_error(sr, NT_STATUS_INVALID_SECURITY_DESCR, 0, 0);
108 108 return (SDRC_ERROR);
109 109 }
110 110
111 111 if (sdlen > xa->smb_mdrcnt) {
112 112 /*
113 113 * The maximum data return count specified by the
114 114 * client is not big enough to hold the security
115 115 * descriptor. We have to return an error but we
116 116 * should provide a buffer size hint for the client.
117 117 */
118 118 (void) smb_mbc_encodef(&xa->rep_param_mb, "l", sdlen);
119 119 err.status = NT_STATUS_BUFFER_TOO_SMALL;
120 120 err.errcls = ERRDOS;
121 121 err.errcode = ERROR_INSUFFICIENT_BUFFER;
122 122 smbsr_set_error(sr, &err);
123 123 smb_sd_term(&sd);
124 124 return (SDRC_SUCCESS);
125 125 }
126 126
127 127 smb_encode_sd(&xa->rep_data_mb, &sd, secinfo);
128 128 (void) smb_mbc_encodef(&xa->rep_param_mb, "l", sdlen);
129 129 smb_sd_term(&sd);
130 130 return (SDRC_SUCCESS);
131 131 }
132 132
133 133 /*
134 134 * smb_nt_transact_set_security_info
135 135 *
136 136 * This command allows the client to change the security descriptor on a
137 137 * file. All we do here is decode the parameters and the data. The data
138 138 * is passed directly to smb_nt_set_security_object, with the security
139 139 * information describing the information to set. There are no response
140 140 * parameters or data.
141 141 *
142 142 * Client Parameter Block Encoding Description
143 143 * ================================== ==================================
144 144 * USHORT Fid; FID of target
145 145 * USHORT Reserved; MBZ
146 146 * ULONG SecurityInformation; Fields of SD that to set
147 147 *
148 148 * Data Block Encoding Description
149 149 * ================================== ==================================
150 150 * Data[TotalDataCount] Security Descriptor information
151 151 */
152 152 smb_sdrc_t
153 153 smb_nt_transact_set_security_info(struct smb_request *sr, struct smb_xa *xa)
154 154 {
155 155 smb_sd_t sd;
156 156 uint32_t secinfo;
157 157 uint32_t status;
158 158
159 159 if (smb_mbc_decodef(&xa->req_param_mb, "w2.l",
160 160 &sr->smb_fid, &secinfo) != 0) {
161 161 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0);
162 162 return (SDRC_ERROR);
163 163 }
164 164
165 165 smbsr_lookup_file(sr);
166 166 if (sr->fid_ofile == NULL) {
167 167 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
168 168 return (SDRC_ERROR);
169 169 }
170 170
171 171 if ((sr->fid_ofile->f_node == NULL) ||
172 172 (sr->fid_ofile->f_ftype != SMB_FTYPE_DISK)) {
173 173 smbsr_error(sr, NT_STATUS_ACCESS_DENIED, 0, 0);
174 174 return (SDRC_ERROR);
175 175 }
176 176
177 177 sr->user_cr = smb_ofile_getcred(sr->fid_ofile);
178 178
179 179 if (SMB_TREE_IS_READONLY(sr)) {
180 180 smbsr_error(sr, NT_STATUS_MEDIA_WRITE_PROTECTED, 0, 0);
181 181 return (SDRC_ERROR);
182 182 }
183 183
184 184 if (sr->tid_tree->t_acltype != ACE_T) {
185 185 /*
186 186 * If target filesystem doesn't support ACE_T acls then
187 187 * don't process SACL
188 188 */
189 189 secinfo &= ~SMB_SACL_SECINFO;
190 190 }
191 191
192 192 if ((secinfo & SMB_ALL_SECINFO) == 0) {
193 193 return (NT_STATUS_SUCCESS);
194 194 }
195 195
196 196 status = smb_decode_sd(&xa->req_data_mb, &sd);
197 197 if (status != NT_STATUS_SUCCESS) {
198 198 smbsr_error(sr, status, 0, 0);
199 199 return (SDRC_ERROR);
200 200 }
201 201
202 202 if (((secinfo & SMB_OWNER_SECINFO) && (sd.sd_owner == NULL)) ||
203 203 ((secinfo & SMB_GROUP_SECINFO) && (sd.sd_group == NULL))) {
204 204 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0);
205 205 return (SDRC_ERROR);
206 206 }
207 207
208 208 if (!smb_node_is_system(sr->fid_ofile->f_node))
209 209 status = smb_sd_write(sr, &sd, secinfo);
210 210
211 211 smb_sd_term(&sd);
212 212 if (status != NT_STATUS_SUCCESS) {
213 213 smbsr_error(sr, status, 0, 0);
214 214 return (SDRC_ERROR);
215 215 }
216 216
217 217 return (SDRC_SUCCESS);
218 218 }
219 219
220 220 /*
221 221 * smb_encode_sd
222 222 *
223 223 * Encodes given security descriptor in the reply buffer.
224 224 */
225 225 void
226 226 smb_encode_sd(mbuf_chain_t *mbc, smb_sd_t *sd, uint32_t secinfo)
227 227 {
228 228 uint32_t offset = SMB_SD_HDRSIZE;
229 229
230 230 /* encode header */
231 231 (void) smb_mbc_encodef(mbc, "b.w",
232 232 sd->sd_revision, sd->sd_control | SE_SELF_RELATIVE);
233 233
234 234 /* owner offset */
235 235 if (secinfo & SMB_OWNER_SECINFO) {
236 236 ASSERT(sd->sd_owner);
237 237 (void) smb_mbc_encodef(mbc, "l", offset);
238 238 offset += smb_sid_len(sd->sd_owner);
239 239 } else {
240 240 (void) smb_mbc_encodef(mbc, "l", 0);
241 241 }
242 242
243 243 /* group offset */
244 244 if (secinfo & SMB_GROUP_SECINFO) {
245 245 ASSERT(sd->sd_group);
246 246 (void) smb_mbc_encodef(mbc, "l", offset);
247 247 offset += smb_sid_len(sd->sd_group);
248 248 } else {
249 249 (void) smb_mbc_encodef(mbc, "l", 0);
250 250 }
251 251
252 252 /* SACL offset */
253 253 if ((secinfo & SMB_SACL_SECINFO) && (sd->sd_sacl)) {
254 254 (void) smb_mbc_encodef(mbc, "l", offset);
255 255 offset += smb_acl_len(sd->sd_sacl);
256 256 } else {
257 257 (void) smb_mbc_encodef(mbc, "l", 0);
258 258 }
259 259
260 260 /* DACL offset */
261 261 if ((secinfo & SMB_DACL_SECINFO) && (sd->sd_dacl))
262 262 (void) smb_mbc_encodef(mbc, "l", offset);
263 263 else
264 264 (void) smb_mbc_encodef(mbc, "l", 0);
265 265
266 266 if (secinfo & SMB_OWNER_SECINFO)
267 267 smb_encode_sid(mbc, sd->sd_owner);
268 268
269 269 if (secinfo & SMB_GROUP_SECINFO)
270 270 smb_encode_sid(mbc, sd->sd_group);
271 271
272 272 if (secinfo & SMB_SACL_SECINFO)
273 273 smb_encode_sacl(mbc, sd->sd_sacl);
274 274
275 275 if (secinfo & SMB_DACL_SECINFO)
276 276 smb_encode_dacl(mbc, sd->sd_dacl);
277 277 }
278 278
279 279 /*
280 280 * smb_encode_sid
281 281 *
282 282 * Encodes given SID in the reply buffer.
283 283 */
284 284 void
285 285 smb_encode_sid(mbuf_chain_t *mbc, smb_sid_t *sid)
286 286 {
287 287 int i;
288 288
289 289 (void) smb_mbc_encodef(mbc, "bb",
290 290 sid->sid_revision, sid->sid_subauthcnt);
291 291
292 292 for (i = 0; i < NT_SID_AUTH_MAX; i++) {
293 293 (void) smb_mbc_encodef(mbc, "b",
294 294 sid->sid_authority[i]);
295 295 }
296 296
297 297 for (i = 0; i < sid->sid_subauthcnt; i++) {
298 298 (void) smb_mbc_encodef(mbc, "l",
299 299 sid->sid_subauth[i]);
300 300 }
301 301 }
302 302
303 303 /*
304 304 * smb_encode_sacl
305 305 *
306 306 * Encodes given SACL in the reply buffer.
307 307 */
308 308 static void
309 309 smb_encode_sacl(mbuf_chain_t *mbc, smb_acl_t *acl)
310 310 {
311 311 smb_ace_t *ace;
312 312 int i;
313 313
314 314 if (acl == NULL)
315 315 return;
316 316
317 317 /* encode header */
318 318 (void) smb_mbc_encodef(mbc, "b.ww2.", acl->sl_revision,
319 319 acl->sl_bsize, acl->sl_acecnt);
320 320
321 321 for (i = 0, ace = acl->sl_aces; i < acl->sl_acecnt; i++, ace++) {
322 322 (void) smb_mbc_encodef(mbc, "bbwl",
323 323 ace->se_hdr.se_type, ace->se_hdr.se_flags,
324 324 ace->se_hdr.se_bsize, ace->se_mask);
325 325
326 326 smb_encode_sid(mbc, ace->se_sid);
327 327 }
328 328 }
329 329
330 330 /*
331 331 * smb_encode_dacl
332 332 *
333 333 * Encodes given DACL in the reply buffer.
334 334 */
335 335 static void
336 336 smb_encode_dacl(mbuf_chain_t *mbc, smb_acl_t *acl)
337 337 {
338 338 smb_ace_t *ace;
339 339
340 340 if (acl == NULL)
341 341 return;
342 342
343 343 /* encode header */
344 344 (void) smb_mbc_encodef(mbc, "b.ww2.", acl->sl_revision,
345 345 acl->sl_bsize, acl->sl_acecnt);
346 346
347 347 ace = list_head(&acl->sl_sorted);
348 348 while (ace) {
349 349 (void) smb_mbc_encodef(mbc, "bbwl",
350 350 ace->se_hdr.se_type, ace->se_hdr.se_flags,
351 351 ace->se_hdr.se_bsize, ace->se_mask);
352 352
353 353 smb_encode_sid(mbc, ace->se_sid);
354 354 ace = list_next(&acl->sl_sorted, ace);
355 355 }
356 356 }
357 357
358 358 /*
359 359 * smb_decode_sd
360 360 *
361 361 * Decodes the security descriptor in the request buffer
362 362 * and set the fields of 'sd' appropraitely. Upon successful
363 363 * return, caller must free allocated memories by calling
|
↓ open down ↓ |
329 lines elided |
↑ open up ↑ |
364 364 * smb_sd_term().
365 365 */
366 366 uint32_t
367 367 smb_decode_sd(mbuf_chain_t *mbc, smb_sd_t *sd)
368 368 {
369 369 struct mbuf_chain sdbuf;
370 370 uint32_t owner_offs;
371 371 uint32_t group_offs;
372 372 uint32_t sacl_offs;
373 373 uint32_t dacl_offs;
374 + int rc;
374 375
375 376 smb_sd_init(sd, SECURITY_DESCRIPTOR_REVISION);
376 377
377 378 (void) MBC_SHADOW_CHAIN(&sdbuf, mbc,
378 379 mbc->chain_offset,
379 380 mbc->max_bytes - mbc->chain_offset);
380 381
381 - if (smb_mbc_decodef(&sdbuf, "b.wllll",
382 + rc = smb_mbc_decodef(&sdbuf, "b.wllll",
382 383 &sd->sd_revision, &sd->sd_control,
383 - &owner_offs, &group_offs, &sacl_offs, &dacl_offs))
384 - goto decode_error;
384 + &owner_offs, &group_offs, &sacl_offs, &dacl_offs);
385 385
386 + /* Prevent disallowed flags in smb_sd_term. */
386 387 sd->sd_control &= ~SE_SELF_RELATIVE;
388 + if (rc != 0)
389 + goto decode_error;
387 390
388 391 if (owner_offs != 0) {
389 392 if (owner_offs < SMB_SD_HDRSIZE)
390 393 goto decode_error;
391 394
392 395 sd->sd_owner = smb_decode_sid(mbc, owner_offs);
393 396 if (sd->sd_owner == NULL)
394 397 goto decode_error;
395 398 }
396 399
397 400 if (group_offs != 0) {
398 401 if (group_offs < SMB_SD_HDRSIZE)
399 402 goto decode_error;
400 403
401 404 sd->sd_group = smb_decode_sid(mbc, group_offs);
402 405 if (sd->sd_group == NULL)
403 406 goto decode_error;
404 407 }
405 408
406 409 if (sacl_offs != 0) {
407 410 if ((sd->sd_control & SE_SACL_PRESENT) == 0)
408 411 goto decode_error;
409 412
410 413 if (sacl_offs < SMB_SD_HDRSIZE)
411 414 goto decode_error;
412 415
413 416 sd->sd_sacl = smb_decode_acl(mbc, sacl_offs);
414 417 if (sd->sd_sacl == NULL)
415 418 goto decode_error;
416 419 }
417 420
418 421 if (dacl_offs != 0) {
419 422 if ((sd->sd_control & SE_DACL_PRESENT) == 0)
420 423 goto decode_error;
421 424
422 425 if (dacl_offs < SMB_SD_HDRSIZE)
423 426 goto decode_error;
424 427
425 428 sd->sd_dacl = smb_decode_acl(mbc, dacl_offs);
426 429 if (sd->sd_dacl == NULL)
427 430 goto decode_error;
428 431 }
429 432
430 433 return (NT_STATUS_SUCCESS);
431 434
432 435 decode_error:
433 436 smb_sd_term(sd);
434 437 return (NT_STATUS_INVALID_SECURITY_DESCR);
435 438 }
436 439
437 440 /*
438 441 * smb_decode_sid
439 442 *
440 443 * Allocates memory and decodes the SID in the request buffer
441 444 * Upon successful return, caller must free the allocated memory
442 445 * by calling smb_sid_free()
443 446 */
444 447 smb_sid_t *
445 448 smb_decode_sid(mbuf_chain_t *mbc, uint32_t offset)
446 449 {
447 450 uint8_t revision;
448 451 uint8_t subauth_cnt;
449 452 struct mbuf_chain sidbuf;
450 453 smb_sid_t *sid;
451 454 int sidlen;
452 455 int bytes_left;
453 456 int i;
454 457
455 458 offset += mbc->chain_offset;
456 459 bytes_left = mbc->max_bytes - offset;
457 460 if (bytes_left < (int)sizeof (smb_sid_t))
458 461 return (NULL);
459 462
460 463 if (MBC_SHADOW_CHAIN(&sidbuf, mbc, offset, bytes_left) != 0)
461 464 return (NULL);
462 465
463 466 if (smb_mbc_decodef(&sidbuf, "bb", &revision, &subauth_cnt))
464 467 return (NULL);
465 468
466 469 sidlen = sizeof (smb_sid_t) - sizeof (uint32_t) +
467 470 (subauth_cnt * sizeof (uint32_t));
468 471 sid = kmem_alloc(sidlen, KM_SLEEP);
469 472
470 473 sid->sid_revision = revision;
471 474 sid->sid_subauthcnt = subauth_cnt;
472 475
473 476 for (i = 0; i < NT_SID_AUTH_MAX; i++) {
474 477 if (smb_mbc_decodef(&sidbuf, "b", &sid->sid_authority[i]))
475 478 goto decode_err;
476 479 }
477 480
478 481 for (i = 0; i < sid->sid_subauthcnt; i++) {
479 482 if (smb_mbc_decodef(&sidbuf, "l", &sid->sid_subauth[i]))
480 483 goto decode_err;
481 484 }
482 485
483 486 return (sid);
484 487
485 488 decode_err:
486 489 kmem_free(sid, sidlen);
487 490 return (NULL);
488 491 }
489 492
490 493 /*
491 494 * smb_decode_acl
492 495 *
493 496 * Allocates memory and decodes the ACL in the request buffer
494 497 * Upon successful return, caller must free the allocated memory
495 498 * by calling smb_acl_free().
496 499 */
497 500 static smb_acl_t *
498 501 smb_decode_acl(mbuf_chain_t *mbc, uint32_t offset)
499 502 {
500 503 struct mbuf_chain aclbuf;
501 504 smb_acl_t *acl;
502 505 smb_ace_t *ace;
503 506 uint8_t revision;
504 507 uint16_t size;
505 508 uint16_t acecnt;
506 509 int bytes_left;
507 510 uint32_t sid_offs = offset;
508 511 int sidlen;
509 512 int i;
510 513
511 514 offset += mbc->chain_offset;
512 515 bytes_left = mbc->max_bytes - offset;
513 516 if (bytes_left < SMB_ACL_HDRSIZE)
514 517 return (NULL);
515 518
516 519 if (MBC_SHADOW_CHAIN(&aclbuf, mbc, offset, bytes_left) != 0)
517 520 return (NULL);
518 521
519 522 if (smb_mbc_decodef(&aclbuf, "b.ww2.", &revision, &size, &acecnt))
520 523 return (NULL);
521 524
522 525 if (size == 0)
523 526 return (NULL);
524 527
525 528 acl = smb_acl_alloc(revision, size, acecnt);
526 529
527 530 sid_offs += SMB_ACL_HDRSIZE;
528 531 for (i = 0, ace = acl->sl_aces; i < acl->sl_acecnt; i++, ace++) {
529 532 if (smb_mbc_decodef(&aclbuf, "bbwl",
530 533 &ace->se_hdr.se_type, &ace->se_hdr.se_flags,
531 534 &ace->se_hdr.se_bsize, &ace->se_mask))
532 535 goto decode_error;
533 536
534 537 sid_offs += SMB_ACE_HDRSIZE + sizeof (ace->se_mask);
535 538 ace->se_sid = smb_decode_sid(mbc, sid_offs);
536 539 if (ace->se_sid == NULL)
537 540 goto decode_error;
538 541 /* This is SID length plus any paddings between ACEs */
539 542 sidlen = ace->se_hdr.se_bsize -
540 543 (SMB_ACE_HDRSIZE + sizeof (ace->se_mask));
541 544 aclbuf.chain_offset += sidlen;
542 545 sid_offs += sidlen;
543 546 }
544 547
545 548 return (acl);
546 549
547 550 decode_error:
548 551 smb_acl_free(acl);
549 552 return (NULL);
550 553 }
|
↓ open down ↓ |
154 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX