Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions openfst/lib/const-fst.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ class ConstFstImpl : public FstImpl<A> {
data->ref_count = nullptr;
}

// Maximum number of arcs during read.
static constexpr uint64_t kMaxArcs = 0x10000000000000ull;
// Maximum number of states.
static constexpr uint64_t kMaxStates = std::min<uint64_t>(
0x10000000000000ull, std::numeric_limits<StateId>::max());

private:
// Used to find narcs_ and nstates_ in Write.
friend class ConstFst<Arc, Unsigned>;
Expand All @@ -134,10 +140,6 @@ class ConstFstImpl : public FstImpl<A> {
static constexpr int kAlignedFileVersion = 1;
// Minimum file format version supported.
static constexpr int kMinFileVersion = 1;
// Maximum number of arcs during read.
static constexpr uint64_t kMaxArcs = 0x10000000000000ull;
// Maximum number of states during read.
static constexpr uint64_t kMaxStates = 0x10000000000000ull;

std::unique_ptr<MappedFile> states_region_; // Mapped file for states.
std::unique_ptr<MappedFile> arcs_region_; // Mapped file for arcs.
Expand All @@ -162,10 +164,21 @@ ConstFstImpl<Arc, Unsigned>::ConstFstImpl(const Fst<Arc>& fst) {
SetOutputSymbols(fst.OutputSymbols());
start_ = fst.Start();
// Counts states and arcs.
uint64_t nstates = 0;
for (StateIterator<Fst<Arc>> siter(fst); !siter.Done(); siter.Next()) {
++nstates_;
++nstates;
narcs_ += fst.NumArcs(siter.Value());
}
if (nstates > kMaxStates) {
FSTERROR() << "ConstFst: Number of states " << nstates
<< " exceeds maximum " << kMaxStates << " for state ID type";
SetProperties(kError, kError);
start_ = kNoStateId;
nstates_ = 0;
narcs_ = 0;
return;
}
nstates_ = nstates;
states_region_.reset(MappedFile::AllocateType<ConstState>(nstates_));
arcs_region_.reset(MappedFile::AllocateType<Arc>(narcs_));
states_ = static_cast<ConstState*>(states_region_->mutable_data());
Expand Down Expand Up @@ -201,19 +214,27 @@ ConstFstImpl<Arc, Unsigned>* ConstFstImpl<Arc, Unsigned>::Read(
auto impl = std::make_unique<ConstFstImpl>();
FstHeader hdr;
if (!impl->ReadHeader(strm, opts, kMinFileVersion, &hdr)) return nullptr;
impl->start_ = hdr.Start();
if (hdr.NumStates() < 0 || hdr.NumStates() > kMaxStates) {
LOG(ERROR) << "ConstFst::Read: Invalid number of states: "
<< hdr.NumStates() << " > " << kMaxStates << " for "
<< opts.source;
return nullptr;
}
impl->nstates_ = hdr.NumStates();
if (impl->nstates_ < 0 || impl->nstates_ > kMaxStates) {
LOG(ERROR) << "ConstFst::Read: Invalid number of states: " << impl->nstates_
<< " > " << kMaxStates << " for " << opts.source;
if (hdr.Start() != kNoStateId &&
(hdr.Start() < 0 || hdr.Start() >= impl->nstates_)) {
LOG(ERROR) << "ConstFst::Read: Invalid start state " << hdr.Start()
<< " for FST with " << impl->nstates_
<< " states: " << opts.source;
return nullptr;
}
impl->narcs_ = hdr.NumArcs();
if (impl->narcs_ < 0 || impl->narcs_ > kMaxArcs) {
LOG(ERROR) << "ConstFst::Read: Invalid number of arcs: " << impl->narcs_
impl->start_ = hdr.Start();
if (hdr.NumArcs() < 0 || hdr.NumArcs() > kMaxArcs) {
LOG(ERROR) << "ConstFst::Read: Invalid number of arcs: " << hdr.NumArcs()
<< " > " << kMaxArcs << " for " << opts.source;
return nullptr;
}
impl->narcs_ = hdr.NumArcs();
// Ensures compatibility.
if (hdr.Version() == kAlignedFileVersion) {
hdr.SetFlags(hdr.GetFlags() | FstHeader::IS_ALIGNED);
Expand Down Expand Up @@ -245,13 +266,6 @@ ConstFstImpl<Arc, Unsigned>* ConstFstImpl<Arc, Unsigned>::Read(
impl->arcs_ = static_cast<Arc*>(impl->arcs_region_->mutable_data());

// Check states.
if (impl->start_ != kNoStateId &&
!(impl->start_ >= 0 && impl->start_ < impl->nstates_)) {
LOG(ERROR) << "ConstFst::Read: Invalid start state " << impl->start_
<< " for FST with " << impl->nstates_
<< " states: " << opts.source;
return nullptr;
}
for (StateId s = 0; s < impl->nstates_; ++s) {
if (impl->states_[s].pos > impl->narcs_ ||
impl->states_[s].narcs > impl->narcs_ - impl->states_[s].pos) {
Expand Down
78 changes: 41 additions & 37 deletions openfst/test/fst_limits_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -158,57 +158,61 @@ TEST(CompactLimitsTest, VariableSizeNonMonotonicOffsetsTest) {

// ConstFst limits tests.

TEST(ConstLimitsTest, MaxStatesTest) {
using StdConstFstImpl = internal::ConstFstImpl<StdArc, uint32_t>;

FstHeader MakeConstHeader(int64_t nstates, int64_t narcs, int64_t start) {
FstHeader hdr;
hdr.SetFstType("const");
hdr.SetArcType(StdArc::Type());
hdr.SetVersion(2);
// kMaxStates for ConstFst is 1LL << 54.
hdr.SetNumStates((1LL << 54) + 1);
hdr.SetNumArcs(0);
hdr.SetStart(0);
hdr.SetNumStates(nstates);
hdr.SetNumArcs(narcs);
hdr.SetStart(start);
return hdr;
}

std::istringstream iss;
FstReadOptions opts;
opts.header = &hdr;
TEST(ConstLimitsTest, MaxStatesTest) {
// Include 1 valid ConstState (20 bytes) in the stream so that a 64-bit count
// like (1LL << 32) + 1 that truncates to 1 when narrowed to int32_t StateId
// would succeed if not validated before narrowing.
const std::string one_state_data(20, '\0');
for (const int64_t nstates :
{static_cast<int64_t>(StdConstFstImpl::kMaxStates + 1),
(int64_t{1} << 32) + 1, (int64_t{1} << 54) + 1, int64_t{-1}}) {
FstHeader hdr = MakeConstHeader(nstates, /*narcs=*/0, /*start=*/0);
std::istringstream iss(one_state_data);
FstReadOptions opts;
opts.header = &hdr;

auto* impl = internal::ConstFstImpl<StdArc, uint32_t>::Read(iss, opts);
EXPECT_EQ(impl, nullptr);
EXPECT_EQ(StdConstFstImpl::Read(iss, opts), nullptr);
}
}

TEST(ConstLimitsTest, MaxArcsTest) {
FstHeader hdr;
hdr.SetFstType("const");
hdr.SetArcType(StdArc::Type());
hdr.SetVersion(2);
hdr.SetNumStates(10);
// kMaxArcs for ConstFst is 1LL << 54.
hdr.SetNumArcs((1LL << 54) + 1);
hdr.SetStart(0);

std::istringstream iss;
FstReadOptions opts;
opts.header = &hdr;
for (const int64_t narcs :
{static_cast<int64_t>(StdConstFstImpl::kMaxArcs + 1), int64_t{-1}}) {
FstHeader hdr = MakeConstHeader(/*nstates=*/10, narcs, /*start=*/0);
std::istringstream iss;
FstReadOptions opts;
opts.header = &hdr;

auto* impl = internal::ConstFstImpl<StdArc, uint32_t>::Read(iss, opts);
EXPECT_EQ(impl, nullptr);
EXPECT_EQ(StdConstFstImpl::Read(iss, opts), nullptr);
}
}

TEST(ConstLimitsTest, StartStateOutOfRangeTest) {
FstHeader hdr;
hdr.SetFstType("const");
hdr.SetArcType(StdArc::Type());
hdr.SetVersion(2);
hdr.SetNumStates(10);
hdr.SetNumArcs(0);
hdr.SetStart(10);

std::istringstream iss;
FstReadOptions opts;
opts.header = &hdr;
// Include 1 valid ConstState (20 bytes) in the stream so that a 64-bit start
// state like (1LL << 32) that truncates to 0 when narrowed to int32_t StateId
// would succeed on a 1-state FST if not validated before narrowing.
const std::string one_state_data(20, '\0');
for (const int64_t start : {int64_t{1}, int64_t{-2}, int64_t{1} << 32}) {
FstHeader hdr = MakeConstHeader(/*nstates=*/1, /*narcs=*/0, start);
std::istringstream iss(one_state_data);
FstReadOptions opts;
opts.header = &hdr;

auto* impl = internal::ConstFstImpl<StdArc, uint32_t>::Read(iss, opts);
EXPECT_EQ(impl, nullptr);
EXPECT_EQ(StdConstFstImpl::Read(iss, opts), nullptr);
}
}

// VectorFst limits tests.
Expand Down
Loading