mirror of
https://github.com/discourse/discourse.git
synced 2026-08-11 02:59:07 +08:00
## Summary The markdown engine generates poll options in an unbounded loop based on user input, enabling an attacker to trigger massive memory allocation and CPU usage in the V8 process. This blocks a global mutex and can cause worker crashes, effectively DoS-ing markdown processing for all users. ## Source - Patch Triage: https://patch.discourse.org/patch-triage/1120 - HackerOne report: https://hackerone.com/reports/3598542 --- 🤖 Auto-generated from the patch diff via Patch Triage. Review carefully before merging. Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com> --------- Co-authored-by: discourse-patch-triage[bot] <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
394 lines
8.8 KiB
Ruby
Vendored
394 lines
8.8 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe DiscoursePoll::PollsValidator do
|
|
subject(:validator) { described_class.new(post) }
|
|
|
|
let(:post) { Fabricate(:post) }
|
|
|
|
describe "#validate_polls" do
|
|
it "ensures that polls have valid arguments" do
|
|
raw = <<~RAW
|
|
[poll type=not_good1 status=not_good2 results=not_good3]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.invalid_argument", argument: "type", value: "not_good1"),
|
|
)
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.invalid_argument", argument: "status", value: "not_good2"),
|
|
)
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.invalid_argument", argument: "results", value: "not_good3"),
|
|
)
|
|
end
|
|
|
|
it "ensures that all possible values are valid" do
|
|
raw = <<~RAW
|
|
[poll type=regular result=always]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(true)
|
|
|
|
raw = <<~RAW
|
|
[poll type=multiple result=on_vote min=1 max=2]
|
|
* 1
|
|
* 2
|
|
* 3
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(true)
|
|
|
|
raw = <<~RAW
|
|
[poll type=number result=on_close min=3 max=7]
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(true)
|
|
end
|
|
|
|
it "ensure that polls have unique names" do
|
|
raw = <<~RAW
|
|
[poll]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
|
|
[poll]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(I18n.t("poll.multiple_polls_without_name"))
|
|
|
|
raw = <<~RAW
|
|
[poll name=test]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
|
|
[poll name=test]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.multiple_polls_with_same_name", name: "test"),
|
|
)
|
|
end
|
|
|
|
it "ensure that polls have unique options" do
|
|
raw = <<~RAW
|
|
[poll]
|
|
* 1
|
|
* 1
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_different_options"))
|
|
|
|
raw = <<~RAW
|
|
[poll name=test]
|
|
* 1
|
|
* 1
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.named_poll_must_have_different_options", name: "test"),
|
|
)
|
|
end
|
|
|
|
it "ensures that polls do not have any blank options" do
|
|
raw = <<~RAW
|
|
[poll]
|
|
* 1
|
|
*
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_must_not_have_any_empty_options"),
|
|
)
|
|
|
|
raw = <<~RAW
|
|
[poll name=test]
|
|
*
|
|
* 1
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.named_poll_must_not_have_any_empty_options", name: "test"),
|
|
)
|
|
end
|
|
|
|
it "ensure that polls have at least 1 option" do
|
|
raw = <<~RAW
|
|
[poll]
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
|
|
|
raw = <<~RAW
|
|
[poll name=test]
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.named_poll_must_have_at_least_1_option", name: "test"),
|
|
)
|
|
end
|
|
|
|
it "ensure that polls options do not exceed site settings" do
|
|
SiteSetting.poll_maximum_options = 2
|
|
|
|
raw = <<~RAW
|
|
[poll]
|
|
* 1
|
|
* 2
|
|
* 3
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options),
|
|
)
|
|
|
|
raw = <<~RAW
|
|
[poll name=test]
|
|
* 1
|
|
* 2
|
|
* 3
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t(
|
|
"poll.named_poll_must_have_less_options",
|
|
name: "test",
|
|
count: SiteSetting.poll_maximum_options,
|
|
),
|
|
)
|
|
end
|
|
|
|
it "rejects number polls whose generated range exceeds poll_maximum_options" do
|
|
SiteSetting.poll_maximum_options = 20
|
|
|
|
post.raw = <<~RAW
|
|
[poll type=number min=1 max=100 step=1]
|
|
[/poll]
|
|
RAW
|
|
|
|
expect(post.valid?).to eq(false)
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options),
|
|
)
|
|
end
|
|
|
|
describe "multiple type polls" do
|
|
it "ensure that min < max" do
|
|
raw = <<~RAW
|
|
[poll type=multiple min=2 max=1]
|
|
* 1
|
|
* 2
|
|
* 3
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
|
|
raw = <<~RAW
|
|
[poll type=multiple min=2 max=1 name=test]
|
|
* 1
|
|
* 2
|
|
* 3
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: "test"),
|
|
)
|
|
end
|
|
|
|
it "ensure max > 0" do
|
|
raw = <<~RAW
|
|
[poll type=multiple max=-2]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
end
|
|
|
|
it "ensure that max <= number of options" do
|
|
raw = <<~RAW
|
|
[poll type=multiple max=3]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
end
|
|
|
|
it "ensure that min >= 0" do
|
|
raw = <<~RAW
|
|
[poll type=multiple min=-1]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
end
|
|
|
|
it "ensure that min cannot be 0" do
|
|
raw = <<~RAW
|
|
[poll type=multiple min=0]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
end
|
|
|
|
it "ensure that min != number of options" do
|
|
raw = <<~RAW
|
|
[poll type=multiple min=2]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
end
|
|
|
|
it "ensure that min < number of options" do
|
|
raw = <<~RAW
|
|
[poll type=multiple min=3]
|
|
* 1
|
|
* 2
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
|
|
expect(post.errors[:base]).to include(
|
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"),
|
|
)
|
|
end
|
|
end
|
|
|
|
it "number type polls are validated" do
|
|
raw = <<~RAW
|
|
[poll type=number min=-5 max=-10 step=-1]
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
expect(post.errors[:base]).to include(
|
|
"Min #{I18n.t("errors.messages.greater_than", count: 0)}",
|
|
)
|
|
expect(post.errors[:base]).to include(
|
|
"Max #{I18n.t("errors.messages.greater_than", count: "min")}",
|
|
)
|
|
expect(post.errors[:base]).to include(
|
|
"Step #{I18n.t("errors.messages.greater_than", count: 0)}",
|
|
)
|
|
|
|
raw = <<~RAW
|
|
[poll type=number min=9999999999 max=9999999999 step=1]
|
|
[/poll]
|
|
RAW
|
|
|
|
post.raw = raw
|
|
expect(post.valid?).to eq(false)
|
|
expect(post.errors[:base]).to include(
|
|
"Min #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}",
|
|
)
|
|
expect(post.errors[:base]).to include(
|
|
"Max #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}",
|
|
)
|
|
expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_at_least_1_option"))
|
|
end
|
|
end
|
|
end
|