Avoid crashes in FPDFAnnot_GetOptionCount() / FPDFAnnot_GetOptionLabel()
Both of these APIs can fail a CHECK() if the input annotation is the
wrong type. Add an embedder test to demonstrate this issue. Then fix the
crash by adding CPDF_FormField::HasOptField() and checking its return
value before calling CPDF_FormField::CountOptions().
Bug: 366102255
Change-Id: I11b6bee3b1defdbabeb4d0301161783436153a34
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/124530
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Tom Sepez <tsepez@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/cpdf_formfield.cpp b/core/fpdfdoc/cpdf_formfield.cpp
index efed7e4..1e7886d 100644
--- a/core/fpdfdoc/cpdf_formfield.cpp
+++ b/core/fpdfdoc/cpdf_formfield.cpp
@@ -60,18 +60,6 @@
}
}
-bool HasOptField(CPDF_FormField::Type type) {
- switch (type) {
- case CPDF_FormField::kCheckBox:
- case CPDF_FormField::kRadioButton:
- case CPDF_FormField::kComboBox:
- case CPDF_FormField::kListBox:
- return true;
- default:
- return false;
- }
-}
-
} // namespace
// static
@@ -592,14 +580,26 @@
return -1;
}
+bool CPDF_FormField::HasOptField() const {
+ switch (GetType()) {
+ case CPDF_FormField::kCheckBox:
+ case CPDF_FormField::kRadioButton:
+ case CPDF_FormField::kComboBox:
+ case CPDF_FormField::kListBox:
+ return true;
+ default:
+ return false;
+ }
+}
+
int CPDF_FormField::CountOptions() const {
- CHECK(HasOptField(GetType()));
+ CHECK(HasOptField());
RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
return pArray ? fxcrt::CollectionSize<int>(*pArray) : 0;
}
WideString CPDF_FormField::GetOptionText(int index, int sub_index) const {
- CHECK(HasOptField(GetType()));
+ CHECK(HasOptField());
RetainPtr<const CPDF_Array> pArray = ToArray(GetFieldAttrInternal("Opt"));
if (!pArray)
return WideString();
diff --git a/core/fpdfdoc/cpdf_formfield.h b/core/fpdfdoc/cpdf_formfield.h
index fa7bf33..c43f89e 100644
--- a/core/fpdfdoc/cpdf_formfield.h
+++ b/core/fpdfdoc/cpdf_formfield.h
@@ -117,6 +117,10 @@
bool SetItemSelection(int index, NotificationOption notify);
int GetDefaultSelectedItem() const;
+
+ bool HasOptField() const;
+
+ // These can only be called if HasOptField() returns true.
int CountOptions() const;
WideString GetOptionLabel(int index) const;
WideString GetOptionValue(int index) const;
diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp
index 5cfbbfa..e42e6db 100644
--- a/fpdfsdk/fpdf_annot.cpp
+++ b/fpdfsdk/fpdf_annot.cpp
@@ -1317,8 +1317,11 @@
FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_GetOptionCount(FPDF_FORMHANDLE hHandle,
FPDF_ANNOTATION annot) {
- const CPDF_FormField* pFormField = GetFormField(hHandle, annot);
- return pFormField ? pFormField->CountOptions() : -1;
+ const CPDF_FormField* form_field = GetFormField(hHandle, annot);
+ if (!form_field || !form_field->HasOptField()) {
+ return -1;
+ }
+ return form_field->CountOptions();
}
FPDF_EXPORT unsigned long FPDF_CALLCONV
@@ -1327,16 +1330,19 @@
int index,
FPDF_WCHAR* buffer,
unsigned long buflen) {
- if (index < 0)
+ if (index < 0) {
return 0;
+ }
- const CPDF_FormField* pFormField = GetFormField(hHandle, annot);
- if (!pFormField || index >= pFormField->CountOptions())
+ const CPDF_FormField* form_field = GetFormField(hHandle, annot);
+ if (!form_field || !form_field->HasOptField() ||
+ index >= form_field->CountOptions()) {
return 0;
+ }
// SAFETY: required from caller.
return Utf16EncodeMaybeCopyAndReturnLength(
- pFormField->GetOptionLabel(index),
+ form_field->GetOptionLabel(index),
UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen)));
}
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index ead0815..7a36b97 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -2357,6 +2357,28 @@
}
}
+TEST_F(FPDFAnnotEmbedderTest, GetOptionCountWrongAnnotationType) {
+ // Open a file with annotations and load its first page.
+ ASSERT_TRUE(OpenDocument("multiple_form_types.pdf"));
+ ScopedEmbedderTestPage page = LoadScopedPage(0);
+ ASSERT_TRUE(page);
+
+ // This annotation is a form field, but does not support Options.
+ ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page.get(), 3));
+ ASSERT_TRUE(annot);
+
+ // Check types.
+ EXPECT_EQ(FPDF_ANNOT_WIDGET, FPDFAnnot_GetSubtype(annot.get()));
+ EXPECT_EQ(FPDF_FORMFIELD_TEXTFIELD,
+ FPDFAnnot_GetFormFieldType(form_handle(), annot.get()));
+
+ // Option APIs do not support this annot type. They gracefully return an error
+ // and do not crash.
+ EXPECT_EQ(-1, FPDFAnnot_GetOptionCount(form_handle(), annot.get()));
+ EXPECT_EQ(
+ 0u, FPDFAnnot_GetOptionLabel(form_handle(), annot.get(), 0, nullptr, 0));
+}
+
TEST_F(FPDFAnnotEmbedderTest, GetOptionLabelCombobox) {
// Open a file with combobox widget annotations and load its first page.
ASSERT_TRUE(OpenDocument("combobox_form.pdf"));