Skip to content

Hazard: annotation_layer.js uses document.getElementsByName without verification #14003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Rob--W opened this issue Sep 11, 2021 · 1 comment · Fixed by #14023
Closed

Hazard: annotation_layer.js uses document.getElementsByName without verification #14003

Rob--W opened this issue Sep 11, 2021 · 1 comment · Fixed by #14023
Assignees

Comments

@Rob--W
Copy link
Member

Rob--W commented Sep 11, 2021

annotation_layer.js has logic to keep input elements in sync. The logic uses document.getElementsByName with a name derived from the PDF to retrieve the set of related elements. This query is not namespaced, so if the viewer ever adds a name attribute to a checkbox or radio input in the page, then the PDF file can get it changed (without user interaction via the scripting interface). Fortunately, there doesn't appear to be any input/radio element in the default viewer code. A custom viewer can be affected by this bug, however.

I suggest to replace the document.getElementsByTagName calls with a function that checks that the elements are created by the annotation_layer.js code before returning them. There are several ways to do so, e.g. by adding a custom attribute to each input element.

Seemingly generic property assignment

This code on its own looks dangerous because it allows the caller to overwrite any property

setPropertyOnSiblings(base, key, value, keyInStorage) {
const storage = this.annotationStorage;
for (const element of document.getElementsByName(base.name)) {
if (element !== base) {
element[key] = value;

Fortunately, it is only called with a fixed key, so only the value property can be updated in response to user input events:
element.addEventListener("input", event => {
storage.setValue(id, { value: event.target.value });
this.setPropertyOnSiblings(
element,
"value",
event.target.value,
"value"
);
});

Unchecking other checkboxes on checkbox state change

element.name = this.data.fieldName;
if (value) {
element.setAttribute("checked", true);
}
element.setAttribute("id", id);
element.tabIndex = DEFAULT_TAB_INDEX;
element.addEventListener("change", function (event) {
const name = event.target.name;
for (const checkbox of document.getElementsByName(name)) {
if (checkbox !== event.target) {
checkbox.checked = false;

Updating the abstract state (storage)

element.addEventListener("change", function (event) {
const { target } = event;
for (const radio of document.getElementsByName(target.name)) {
if (radio !== target) {
storage.setValue(radio.getAttribute("id"), { value: false });

Allowing scripts to uncheck checkbox/radio inputs

The others require user interaction to have any effect; this one allows scripts embedded in the PDF to run logic without user interaction.

element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
value(event) {
const checked = pdfButtonValue === event.detail.value;
for (const radio of document.getElementsByName(event.target.name)) {

@timvandermeij
Copy link
Contributor

/cc @calixteman

@calixteman calixteman self-assigned this Sep 12, 2021
calixteman added a commit to calixteman/pdf.js that referenced this issue Sep 12, 2021
  - it aims to fix mozilla#14003.
  - in a custom viewer, the use of document.getElementsByName could lead to get some elements which don't correspond to an annotation, so this patch removes it and annotations with the same fieldName are collected when they're created.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment