Skip to content

Commit 21464e7

Browse files
authored
[Custom Descriptors] Validate features in TypeBuilder (#7790)
The Binaryen validator does not validate all types because it does not want to pay the cost of walking the module to gather the types. Furthermore, even if it did walk the module to gather types, it would still have no way of validating completely unused types. However, correctly validating that a module's types use only the allowed features can be important, for example to prevent the fuzzer from using initial contents that use a feature with a handler that cannot handle that feature. To achieve this validation without paying the cost in the validator, validate features used by types in TypeBuilder instead. For now the only feature validated is custom descriptors. To avoid introducing new errors, ensure that all features are available when parsing in wasm-dis and wasm-reduce. Also tweak error messages to make the binary and text parsers consistent so we can simplify the test.
1 parent 73be9dc commit 21464e7

File tree

9 files changed

+72
-25
lines changed

9 files changed

+72
-25
lines changed

src/parser/parse-2-typedefs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Result<> parseTypeDefs(
2424
IndexMap& typeIndices,
2525
std::vector<HeapType>& types,
2626
std::unordered_map<HeapType, std::unordered_map<Name, Index>>& typeNames) {
27-
TypeBuilder builder(decls.typeDefs.size());
27+
TypeBuilder builder(decls.typeDefs.size(), decls.wasm.features);
2828
ParseTypeDefsCtx ctx(input, builder, typeIndices);
2929
for (auto& recType : decls.recTypeDefs) {
3030
WithPosition with(ctx, recType.pos);

src/parser/wast-parser.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "lexer.h"
1818
#include "literal.h"
19+
#include "wasm-features.h"
1920
#include "wat-parser.h"
2021

2122
namespace wasm::WATParser {
@@ -123,6 +124,7 @@ Result<WASTModule> wastModule(Lexer& in, bool maybeInvalid = false) {
123124
// start and parse it normally.
124125
in = std::move(reset);
125126
auto wasm = std::make_shared<Module>();
127+
wasm->features = FeatureSet::All;
126128
CHECK_ERR(parseModule(*wasm, in));
127129
return wasm;
128130
}
@@ -479,6 +481,7 @@ Result<WASTScript> wast(Lexer& in) {
479481
// The entire script might be a single module comprising a sequence of
480482
// module fields with a top-level `(module ...)`.
481483
auto wasm = std::make_shared<Module>();
484+
wasm->features = FeatureSet::All;
482485
auto parsed = parseModule(*wasm, in.buffer);
483486
if (parsed.getErr()) {
484487
// No, that wasn't the problem. Return the original error.

src/tools/wasm-dis.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "source-map.h"
2222
#include "support/colors.h"
2323
#include "support/file.h"
24+
#include "wasm-features.h"
2425
#include "wasm-io.h"
2526

2627
#include "tool-options.h"
@@ -66,6 +67,11 @@ int main(int argc, const char* argv[]) {
6667
}
6768
Module wasm;
6869
options.applyOptionsBeforeParse(wasm);
70+
// Temporarily apply all features during parsing to avoid any errors. See note
71+
// below on skipping validation.
72+
auto enabledFeatures = wasm.features;
73+
wasm.features = FeatureSet::All;
74+
6975
try {
7076
ModuleReader().readBinary(options.extra["infile"], wasm, sourceMapFilename);
7177
} catch (ParseException& p) {
@@ -91,6 +97,7 @@ int main(int argc, const char* argv[]) {
9197
// better to have an "autodetect" code path that enables used features
9298
// eventually.
9399

100+
wasm.features = enabledFeatures;
94101
if (options.debug) {
95102
std::cerr << "Printing..." << std::endl;
96103
}

src/tools/wasm-reduce.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ struct Reducer
379379

380380
toolOptions.applyOptionsBeforeParse(*module);
381381

382+
// Assume we may need all features.
383+
module->features = FeatureSet::All;
384+
382385
ModuleReader reader;
383386
try {
384387
reader.read(working, *module);
@@ -390,12 +393,6 @@ struct Reducer
390393

391394
toolOptions.applyOptionsAfterParse(*module);
392395

393-
// If there is no features section, assume we may need them all (without
394-
// this, a module with no features section but that uses e.g. atomics and
395-
// bulk memory would not work).
396-
if (!module->hasFeaturesSection) {
397-
module->features = FeatureSet::All;
398-
}
399396
builder = std::make_unique<Builder>(*module);
400397
setModule(module.get());
401398
}

src/wasm-binary.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1705,7 +1705,7 @@ class WasmBinaryReader {
17051705

17061706
static Name escape(Name name);
17071707
void readNames(size_t sectionPos, size_t payloadLen);
1708-
void readFeatures(size_t payloadLen);
1708+
void readFeatures(size_t sectionPos, size_t payloadLen);
17091709
void readDylink(size_t payloadLen);
17101710
void readDylink0(size_t payloadLen);
17111711

src/wasm-type.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ struct TypeBuilder {
717717
struct Impl;
718718
std::unique_ptr<Impl> impl;
719719

720-
TypeBuilder(size_t n);
720+
TypeBuilder(size_t n, FeatureSet features = FeatureSet::All);
721721
TypeBuilder() : TypeBuilder(0) {}
722722
~TypeBuilder();
723723

@@ -862,6 +862,8 @@ struct TypeBuilder {
862862
InvalidUnsharedDescriptor,
863863
// A non-shared type described by a shared type.
864864
InvalidUnsharedDescribes,
865+
// The custom descriptors feature is missing.
866+
RequiresCustomDescriptors,
865867
};
866868

867869
struct Error {

src/wasm/wasm-binary.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2009,6 +2009,8 @@ void WasmBinaryReader::preScan() {
20092009
} else if (debugInfo &&
20102010
sectionName == BinaryConsts::CustomSections::Name) {
20112011
readNames(oldPos, payloadLen);
2012+
} else if (sectionName == BinaryConsts::CustomSections::TargetFeatures) {
2013+
readFeatures(oldPos, payloadLen);
20122014
}
20132015
// TODO: We could stop early in some cases, if we've seen enough (e.g.
20142016
// seeing Code implies no BranchHint will appear, due to ordering).
@@ -2139,11 +2141,11 @@ void WasmBinaryReader::readCustomSection(size_t payloadLen) {
21392141
throwError("bad user section size");
21402142
}
21412143
payloadLen -= read;
2142-
if (sectionName.equals(BinaryConsts::CustomSections::Name)) {
2143-
// We already read the name section before anything else.
2144+
if (sectionName.equals(BinaryConsts::CustomSections::Name) ||
2145+
sectionName.equals(BinaryConsts::CustomSections::TargetFeatures)) {
2146+
// We already read the name and target features sections before anything
2147+
// else.
21442148
pos += payloadLen;
2145-
} else if (sectionName.equals(BinaryConsts::CustomSections::TargetFeatures)) {
2146-
readFeatures(payloadLen);
21472149
} else if (sectionName.equals(BinaryConsts::CustomSections::Dylink)) {
21482150
readDylink(payloadLen);
21492151
} else if (sectionName.equals(BinaryConsts::CustomSections::Dylink0)) {
@@ -2550,7 +2552,7 @@ void WasmBinaryReader::readMemories() {
25502552
}
25512553

25522554
void WasmBinaryReader::readTypes() {
2553-
TypeBuilder builder(getU32LEB());
2555+
TypeBuilder builder(getU32LEB(), wasm.features);
25542556

25552557
auto readHeapType = [&]() -> std::pair<HeapType, Exactness> {
25562558
int64_t htCode = getS64LEB(); // TODO: Actually s33
@@ -2738,7 +2740,7 @@ void WasmBinaryReader::readTypes() {
27382740

27392741
auto result = builder.build();
27402742
if (auto* err = result.getError()) {
2741-
Fatal() << "Invalid type: " << err->reason << " at index " << err->index;
2743+
Fatal() << "invalid type: " << err->reason << " at index " << err->index;
27422744
}
27432745
types = std::move(*result);
27442746

@@ -5180,10 +5182,9 @@ void WasmBinaryReader::readNames(size_t sectionPos, size_t payloadLen) {
51805182
}
51815183
}
51825184

5183-
void WasmBinaryReader::readFeatures(size_t payloadLen) {
5185+
void WasmBinaryReader::readFeatures(size_t sectionPos, size_t payloadLen) {
51845186
wasm.hasFeaturesSection = true;
51855187

5186-
auto sectionPos = pos;
51875188
size_t numFeatures = getU32LEB();
51885189
for (size_t i = 0; i < numFeatures; ++i) {
51895190
uint8_t prefix = getInt8();

src/wasm/wasm-type.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,8 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) {
14561456
return os << "Heap type has an invalid unshared descriptor";
14571457
case TypeBuilder::ErrorReason::InvalidUnsharedDescribes:
14581458
return os << "Heap type describes an invalid unshared type";
1459+
case TypeBuilder::ErrorReason::RequiresCustomDescriptors:
1460+
return os << "custom descriptors required but not enabled";
14591461
}
14601462
WASM_UNREACHABLE("Unexpected error reason");
14611463
}
@@ -2251,11 +2253,14 @@ struct TypeBuilder::Impl {
22512253

22522254
std::vector<Entry> entries;
22532255

2254-
Impl(size_t n) : entries(n) {}
2256+
// We will validate features as we go.
2257+
FeatureSet features;
2258+
2259+
Impl(size_t n, FeatureSet features) : entries(n), features(features) {}
22552260
};
22562261

2257-
TypeBuilder::TypeBuilder(size_t n) {
2258-
impl = std::make_unique<TypeBuilder::Impl>(n);
2262+
TypeBuilder::TypeBuilder(size_t n, FeatureSet features) {
2263+
impl = std::make_unique<TypeBuilder::Impl>(n, features);
22592264
}
22602265

22612266
TypeBuilder::~TypeBuilder() = default;
@@ -2403,7 +2408,9 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) {
24032408
}
24042409

24052410
std::optional<TypeBuilder::ErrorReason>
2406-
validateType(HeapTypeInfo& info, std::unordered_set<HeapType>& seenTypes) {
2411+
validateType(HeapTypeInfo& info,
2412+
std::unordered_set<HeapType>& seenTypes,
2413+
FeatureSet features) {
24072414
if (auto* super = info.supertype) {
24082415
// The supertype must be canonical (i.e. defined in a previous rec group)
24092416
// or have already been defined in this rec group.
@@ -2416,6 +2423,9 @@ validateType(HeapTypeInfo& info, std::unordered_set<HeapType>& seenTypes) {
24162423
}
24172424
}
24182425
if (auto* desc = info.described) {
2426+
if (!features.hasCustomDescriptors()) {
2427+
return TypeBuilder::ErrorReason::RequiresCustomDescriptors;
2428+
}
24192429
if (info.kind != HeapTypeKind::Struct) {
24202430
return TypeBuilder::ErrorReason::NonStructDescribes;
24212431
}
@@ -2428,6 +2438,9 @@ validateType(HeapTypeInfo& info, std::unordered_set<HeapType>& seenTypes) {
24282438
}
24292439
}
24302440
if (auto* desc = info.descriptor) {
2441+
if (!features.hasCustomDescriptors()) {
2442+
return TypeBuilder::ErrorReason::RequiresCustomDescriptors;
2443+
}
24312444
if (info.kind != HeapTypeKind::Struct) {
24322445
return TypeBuilder::ErrorReason::NonStructDescriptor;
24332446
}
@@ -2544,7 +2557,8 @@ void updateReferencedHeapTypes(
25442557
TypeBuilder::BuildResult
25452558
buildRecGroup(std::unique_ptr<RecGroupInfo>&& groupInfo,
25462559
std::vector<std::unique_ptr<HeapTypeInfo>>&& typeInfos,
2547-
std::unordered_map<HeapType, HeapType>& canonicalized) {
2560+
std::unordered_map<HeapType, HeapType>& canonicalized,
2561+
FeatureSet features) {
25482562
// First, we need to replace any referenced temporary HeapTypes from
25492563
// previously built groups with their canonicalized versions.
25502564
for (auto& info : typeInfos) {
@@ -2555,7 +2569,7 @@ buildRecGroup(std::unique_ptr<RecGroupInfo>&& groupInfo,
25552569
std::unordered_set<HeapType> seenTypes;
25562570
for (size_t i = 0; i < typeInfos.size(); ++i) {
25572571
auto& info = typeInfos[i];
2558-
if (auto err = validateType(*info, seenTypes)) {
2572+
if (auto err = validateType(*info, seenTypes, features)) {
25592573
return {TypeBuilder::Error{i, *err}};
25602574
}
25612575
seenTypes.insert(asHeapType(info));
@@ -2661,8 +2675,10 @@ TypeBuilder::BuildResult TypeBuilder::build() {
26612675
typeInfos.emplace_back(std::move(impl->entries[groupStart + i].info));
26622676
}
26632677

2664-
auto built =
2665-
buildRecGroup(std::move(groupInfo), std::move(typeInfos), canonicalized);
2678+
auto built = buildRecGroup(std::move(groupInfo),
2679+
std::move(typeInfos),
2680+
canonicalized,
2681+
impl->features);
26662682
if (auto* error = built.getError()) {
26672683
return {TypeBuilder::Error{groupStart + error->index, error->reason}};
26682684
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
;; Test that we reject descriptor types when custom descriptors are not enabled,
2+
;; even if they are not directly used.
3+
4+
;; RUN: not wasm-opt -all --disable-custom-descriptors %s 2>&1 | filecheck %s
5+
6+
;; Check the binary parser, too.
7+
8+
;; RUN: wasm-opt -all %s -o %t.wasm
9+
;; RUN: not wasm-opt -all --disable-custom-descriptors %t.wasm 2>&1 | filecheck %s
10+
11+
;; CHECK: invalid type: custom descriptors required but not enabled
12+
13+
(module
14+
(rec
15+
(type $struct (descriptor $desc (struct)))
16+
(type $desc (describes $struct (struct)))
17+
(type $used (struct))
18+
)
19+
20+
(global $use (ref null $used) (ref.null none))
21+
)

0 commit comments

Comments
 (0)