1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 define("mojo/public/js/validator", [ 6 "mojo/public/js/codec", 7 ], function(codec) { 8 9 var validationError = { 10 NONE: 'VALIDATION_ERROR_NONE', 11 MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT', 12 ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE', 13 UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER', 14 UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER', 15 ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE', 16 UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE', 17 ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER', 18 UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER', 19 MESSAGE_HEADER_INVALID_FLAGS: 20 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS', 21 MESSAGE_HEADER_MISSING_REQUEST_ID: 22 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID', 23 DIFFERENT_SIZED_ARRAYS_IN_MAP: 24 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP', 25 INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE', 26 UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION', 27 UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE', 28 }; 29 30 var NULL_MOJO_POINTER = "NULL_MOJO_POINTER"; 31 32 function isEnumClass(cls) { 33 return cls instanceof codec.Enum; 34 } 35 36 function isStringClass(cls) { 37 return cls === codec.String || cls === codec.NullableString; 38 } 39 40 function isHandleClass(cls) { 41 return cls === codec.Handle || cls === codec.NullableHandle; 42 } 43 44 function isInterfaceClass(cls) { 45 return cls instanceof codec.Interface; 46 } 47 48 function isInterfaceRequestClass(cls) { 49 return cls === codec.InterfaceRequest || 50 cls === codec.NullableInterfaceRequest; 51 } 52 53 function isNullable(type) { 54 return type === codec.NullableString || type === codec.NullableHandle || 55 type === codec.NullableInterface || 56 type === codec.NullableInterfaceRequest || 57 type instanceof codec.NullableArrayOf || 58 type instanceof codec.NullablePointerTo; 59 } 60 61 function Validator(message) { 62 this.message = message; 63 this.offset = 0; 64 this.handleIndex = 0; 65 } 66 67 Object.defineProperty(Validator.prototype, "offsetLimit", { 68 get: function() { return this.message.buffer.byteLength; } 69 }); 70 71 Object.defineProperty(Validator.prototype, "handleIndexLimit", { 72 get: function() { return this.message.handles.length; } 73 }); 74 75 // True if we can safely allocate a block of bytes from start to 76 // to start + numBytes. 77 Validator.prototype.isValidRange = function(start, numBytes) { 78 // Only positive JavaScript integers that are less than 2^53 79 // (Number.MAX_SAFE_INTEGER) can be represented exactly. 80 if (start < this.offset || numBytes <= 0 || 81 !Number.isSafeInteger(start) || 82 !Number.isSafeInteger(numBytes)) 83 return false; 84 85 var newOffset = start + numBytes; 86 if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit) 87 return false; 88 89 return true; 90 }; 91 92 Validator.prototype.claimRange = function(start, numBytes) { 93 if (this.isValidRange(start, numBytes)) { 94 this.offset = start + numBytes; 95 return true; 96 } 97 return false; 98 }; 99 100 Validator.prototype.claimHandle = function(index) { 101 if (index === codec.kEncodedInvalidHandleValue) 102 return true; 103 104 if (index < this.handleIndex || index >= this.handleIndexLimit) 105 return false; 106 107 // This is safe because handle indices are uint32. 108 this.handleIndex = index + 1; 109 return true; 110 }; 111 112 Validator.prototype.validateEnum = function(offset, enumClass) { 113 // Note: Assumes that enums are always 32 bits! But this matches 114 // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay. 115 var value = this.message.buffer.getInt32(offset); 116 return enumClass.validate(value); 117 } 118 119 Validator.prototype.validateHandle = function(offset, nullable) { 120 var index = this.message.buffer.getUint32(offset); 121 122 if (index === codec.kEncodedInvalidHandleValue) 123 return nullable ? 124 validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE; 125 126 if (!this.claimHandle(index)) 127 return validationError.ILLEGAL_HANDLE; 128 129 return validationError.NONE; 130 }; 131 132 Validator.prototype.validateInterface = function(offset, nullable) { 133 return this.validateHandle(offset, nullable); 134 }; 135 136 Validator.prototype.validateInterfaceRequest = function(offset, nullable) { 137 return this.validateHandle(offset, nullable); 138 }; 139 140 Validator.prototype.validateStructHeader = function(offset, minNumBytes) { 141 if (!codec.isAligned(offset)) 142 return validationError.MISALIGNED_OBJECT; 143 144 if (!this.isValidRange(offset, codec.kStructHeaderSize)) 145 return validationError.ILLEGAL_MEMORY_RANGE; 146 147 var numBytes = this.message.buffer.getUint32(offset); 148 149 if (numBytes < minNumBytes) 150 return validationError.UNEXPECTED_STRUCT_HEADER; 151 152 if (!this.claimRange(offset, numBytes)) 153 return validationError.ILLEGAL_MEMORY_RANGE; 154 155 return validationError.NONE; 156 }; 157 158 Validator.prototype.validateStructVersion = function(offset, versionSizes) { 159 var numBytes = this.message.buffer.getUint32(offset); 160 var version = this.message.buffer.getUint32(offset + 4); 161 162 if (version <= versionSizes[versionSizes.length - 1].version) { 163 // Scan in reverse order to optimize for more recent versionSizes. 164 for (var i = versionSizes.length - 1; i >= 0; --i) { 165 if (version >= versionSizes[i].version) { 166 if (numBytes == versionSizes[i].numBytes) 167 break; 168 return validationError.UNEXPECTED_STRUCT_HEADER; 169 } 170 } 171 } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) { 172 return validationError.UNEXPECTED_STRUCT_HEADER; 173 } 174 175 return validationError.NONE; 176 }; 177 178 Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) { 179 var structVersion = this.message.buffer.getUint32(offset + 4); 180 return fieldVersion <= structVersion; 181 }; 182 183 Validator.prototype.validateMessageHeader = function() { 184 185 var err = this.validateStructHeader(0, codec.kMessageHeaderSize); 186 if (err != validationError.NONE) 187 return err; 188 189 var numBytes = this.message.getHeaderNumBytes(); 190 var version = this.message.getHeaderVersion(); 191 192 var validVersionAndNumBytes = 193 (version == 0 && numBytes == codec.kMessageHeaderSize) || 194 (version == 1 && 195 numBytes == codec.kMessageWithRequestIDHeaderSize) || 196 (version > 1 && 197 numBytes >= codec.kMessageWithRequestIDHeaderSize); 198 if (!validVersionAndNumBytes) 199 return validationError.UNEXPECTED_STRUCT_HEADER; 200 201 var expectsResponse = this.message.expectsResponse(); 202 var isResponse = this.message.isResponse(); 203 204 if (version == 0 && (expectsResponse || isResponse)) 205 return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID; 206 207 if (isResponse && expectsResponse) 208 return validationError.MESSAGE_HEADER_INVALID_FLAGS; 209 210 return validationError.NONE; 211 }; 212 213 Validator.prototype.validateMessageIsRequestWithoutResponse = function() { 214 if (this.message.isResponse() || this.message.expectsResponse()) { 215 return validationError.MESSAGE_HEADER_INVALID_FLAGS; 216 } 217 return validationError.NONE; 218 }; 219 220 Validator.prototype.validateMessageIsRequestExpectingResponse = function() { 221 if (this.message.isResponse() || !this.message.expectsResponse()) { 222 return validationError.MESSAGE_HEADER_INVALID_FLAGS; 223 } 224 return validationError.NONE; 225 }; 226 227 Validator.prototype.validateMessageIsResponse = function() { 228 if (this.message.expectsResponse() || !this.message.isResponse()) { 229 return validationError.MESSAGE_HEADER_INVALID_FLAGS; 230 } 231 return validationError.NONE; 232 }; 233 234 // Returns the message.buffer relative offset this pointer "points to", 235 // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the 236 // pointer's value is not valid. 237 Validator.prototype.decodePointer = function(offset) { 238 var pointerValue = this.message.buffer.getUint64(offset); 239 if (pointerValue === 0) 240 return NULL_MOJO_POINTER; 241 var bufferOffset = offset + pointerValue; 242 return Number.isSafeInteger(bufferOffset) ? bufferOffset : null; 243 }; 244 245 Validator.prototype.decodeUnionSize = function(offset) { 246 return this.message.buffer.getUint32(offset); 247 }; 248 249 Validator.prototype.decodeUnionTag = function(offset) { 250 return this.message.buffer.getUint32(offset + 4); 251 }; 252 253 Validator.prototype.validateArrayPointer = function( 254 offset, elementSize, elementType, nullable, expectedDimensionSizes, 255 currentDimension) { 256 var arrayOffset = this.decodePointer(offset); 257 if (arrayOffset === null) 258 return validationError.ILLEGAL_POINTER; 259 260 if (arrayOffset === NULL_MOJO_POINTER) 261 return nullable ? 262 validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; 263 264 return this.validateArray(arrayOffset, elementSize, elementType, 265 expectedDimensionSizes, currentDimension); 266 }; 267 268 Validator.prototype.validateStructPointer = function( 269 offset, structClass, nullable) { 270 var structOffset = this.decodePointer(offset); 271 if (structOffset === null) 272 return validationError.ILLEGAL_POINTER; 273 274 if (structOffset === NULL_MOJO_POINTER) 275 return nullable ? 276 validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; 277 278 return structClass.validate(this, structOffset); 279 }; 280 281 Validator.prototype.validateUnion = function( 282 offset, unionClass, nullable) { 283 var size = this.message.buffer.getUint32(offset); 284 if (size == 0) { 285 return nullable ? 286 validationError.NONE : validationError.UNEXPECTED_NULL_UNION; 287 } 288 289 return unionClass.validate(this, offset); 290 }; 291 292 Validator.prototype.validateNestedUnion = function( 293 offset, unionClass, nullable) { 294 var unionOffset = this.decodePointer(offset); 295 if (unionOffset === null) 296 return validationError.ILLEGAL_POINTER; 297 298 if (unionOffset === NULL_MOJO_POINTER) 299 return nullable ? 300 validationError.NONE : validationError.UNEXPECTED_NULL_UNION; 301 302 return this.validateUnion(unionOffset, unionClass, nullable); 303 }; 304 305 // This method assumes that the array at arrayPointerOffset has 306 // been validated. 307 308 Validator.prototype.arrayLength = function(arrayPointerOffset) { 309 var arrayOffset = this.decodePointer(arrayPointerOffset); 310 return this.message.buffer.getUint32(arrayOffset + 4); 311 }; 312 313 Validator.prototype.validateMapPointer = function( 314 offset, mapIsNullable, keyClass, valueClass, valueIsNullable) { 315 // Validate the implicit map struct: 316 // struct {array<keyClass> keys; array<valueClass> values}; 317 var structOffset = this.decodePointer(offset); 318 if (structOffset === null) 319 return validationError.ILLEGAL_POINTER; 320 321 if (structOffset === NULL_MOJO_POINTER) 322 return mapIsNullable ? 323 validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; 324 325 var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize; 326 var err = this.validateStructHeader(structOffset, mapEncodedSize); 327 if (err !== validationError.NONE) 328 return err; 329 330 // Validate the keys array. 331 var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize; 332 err = this.validateArrayPointer( 333 keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0); 334 if (err !== validationError.NONE) 335 return err; 336 337 // Validate the values array. 338 var valuesArrayPointerOffset = keysArrayPointerOffset + 8; 339 var valuesArrayDimensions = [0]; // Validate the actual length below. 340 if (valueClass instanceof codec.ArrayOf) 341 valuesArrayDimensions = 342 valuesArrayDimensions.concat(valueClass.dimensions()); 343 var err = this.validateArrayPointer(valuesArrayPointerOffset, 344 valueClass.encodedSize, 345 valueClass, 346 valueIsNullable, 347 valuesArrayDimensions, 348 0); 349 if (err !== validationError.NONE) 350 return err; 351 352 // Validate the lengths of the keys and values arrays. 353 var keysArrayLength = this.arrayLength(keysArrayPointerOffset); 354 var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset); 355 if (keysArrayLength != valuesArrayLength) 356 return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP; 357 358 return validationError.NONE; 359 }; 360 361 Validator.prototype.validateStringPointer = function(offset, nullable) { 362 return this.validateArrayPointer( 363 offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0); 364 }; 365 366 // Similar to Array_Data<T>::Validate() 367 // mojo/public/cpp/bindings/lib/array_internal.h 368 369 Validator.prototype.validateArray = 370 function (offset, elementSize, elementType, expectedDimensionSizes, 371 currentDimension) { 372 if (!codec.isAligned(offset)) 373 return validationError.MISALIGNED_OBJECT; 374 375 if (!this.isValidRange(offset, codec.kArrayHeaderSize)) 376 return validationError.ILLEGAL_MEMORY_RANGE; 377 378 var numBytes = this.message.buffer.getUint32(offset); 379 var numElements = this.message.buffer.getUint32(offset + 4); 380 381 // Note: this computation is "safe" because elementSize <= 8 and 382 // numElements is a uint32. 383 var elementsTotalSize = (elementType === codec.PackedBool) ? 384 Math.ceil(numElements / 8) : (elementSize * numElements); 385 386 if (numBytes < codec.kArrayHeaderSize + elementsTotalSize) 387 return validationError.UNEXPECTED_ARRAY_HEADER; 388 389 if (expectedDimensionSizes[currentDimension] != 0 && 390 numElements != expectedDimensionSizes[currentDimension]) { 391 return validationError.UNEXPECTED_ARRAY_HEADER; 392 } 393 394 if (!this.claimRange(offset, numBytes)) 395 return validationError.ILLEGAL_MEMORY_RANGE; 396 397 // Validate the array's elements if they are pointers or handles. 398 399 var elementsOffset = offset + codec.kArrayHeaderSize; 400 var nullable = isNullable(elementType); 401 402 if (isHandleClass(elementType)) 403 return this.validateHandleElements(elementsOffset, numElements, nullable); 404 if (isInterfaceClass(elementType)) 405 return this.validateInterfaceElements( 406 elementsOffset, numElements, nullable); 407 if (isInterfaceRequestClass(elementType)) 408 return this.validateInterfaceRequestElements( 409 elementsOffset, numElements, nullable); 410 if (isStringClass(elementType)) 411 return this.validateArrayElements( 412 elementsOffset, numElements, codec.Uint8, nullable, [0], 0); 413 if (elementType instanceof codec.PointerTo) 414 return this.validateStructElements( 415 elementsOffset, numElements, elementType.cls, nullable); 416 if (elementType instanceof codec.ArrayOf) 417 return this.validateArrayElements( 418 elementsOffset, numElements, elementType.cls, nullable, 419 expectedDimensionSizes, currentDimension + 1); 420 if (isEnumClass(elementType)) 421 return this.validateEnumElements(elementsOffset, numElements, 422 elementType.cls); 423 424 return validationError.NONE; 425 }; 426 427 // Note: the |offset + i * elementSize| computation in the validateFooElements 428 // methods below is "safe" because elementSize <= 8, offset and 429 // numElements are uint32, and 0 <= i < numElements. 430 431 Validator.prototype.validateHandleElements = 432 function(offset, numElements, nullable) { 433 var elementSize = codec.Handle.encodedSize; 434 for (var i = 0; i < numElements; i++) { 435 var elementOffset = offset + i * elementSize; 436 var err = this.validateHandle(elementOffset, nullable); 437 if (err != validationError.NONE) 438 return err; 439 } 440 return validationError.NONE; 441 }; 442 443 Validator.prototype.validateInterfaceElements = 444 function(offset, numElements, nullable) { 445 var elementSize = codec.Interface.prototype.encodedSize; 446 for (var i = 0; i < numElements; i++) { 447 var elementOffset = offset + i * elementSize; 448 var err = this.validateInterface(elementOffset, nullable); 449 if (err != validationError.NONE) 450 return err; 451 } 452 return validationError.NONE; 453 }; 454 455 Validator.prototype.validateInterfaceRequestElements = 456 function(offset, numElements, nullable) { 457 var elementSize = codec.InterfaceRequest.encodedSize; 458 for (var i = 0; i < numElements; i++) { 459 var elementOffset = offset + i * elementSize; 460 var err = this.validateInterfaceRequest(elementOffset, nullable); 461 if (err != validationError.NONE) 462 return err; 463 } 464 return validationError.NONE; 465 }; 466 467 // The elementClass parameter is the element type of the element arrays. 468 Validator.prototype.validateArrayElements = 469 function(offset, numElements, elementClass, nullable, 470 expectedDimensionSizes, currentDimension) { 471 var elementSize = codec.PointerTo.prototype.encodedSize; 472 for (var i = 0; i < numElements; i++) { 473 var elementOffset = offset + i * elementSize; 474 var err = this.validateArrayPointer( 475 elementOffset, elementClass.encodedSize, elementClass, nullable, 476 expectedDimensionSizes, currentDimension); 477 if (err != validationError.NONE) 478 return err; 479 } 480 return validationError.NONE; 481 }; 482 483 Validator.prototype.validateStructElements = 484 function(offset, numElements, structClass, nullable) { 485 var elementSize = codec.PointerTo.prototype.encodedSize; 486 for (var i = 0; i < numElements; i++) { 487 var elementOffset = offset + i * elementSize; 488 var err = 489 this.validateStructPointer(elementOffset, structClass, nullable); 490 if (err != validationError.NONE) 491 return err; 492 } 493 return validationError.NONE; 494 }; 495 496 Validator.prototype.validateEnumElements = 497 function(offset, numElements, enumClass) { 498 var elementSize = codec.Enum.prototype.encodedSize; 499 for (var i = 0; i < numElements; i++) { 500 var elementOffset = offset + i * elementSize; 501 var err = this.validateEnum(elementOffset, enumClass); 502 if (err != validationError.NONE) 503 return err; 504 } 505 return validationError.NONE; 506 }; 507 508 var exports = {}; 509 exports.validationError = validationError; 510 exports.Validator = Validator; 511 return exports; 512 }); 513