In the movie Man of the Year, Robin Williams plays a Jon Stewart-esque comedian who runs for President of the United States. He wins the general election due to a programming glitch in some e-ballot machines deployed nationwide.
In Belgium, as Adrien F. can attest, this very nearly happened.
The city of Brussels has used electronic voting since 1994. Delacroy Europe implemented the first electronic ballots for the city; the firm developed their e-ballot program in C, which ran on floppy disks and stored votes on plastic cards using magnetic strips. Every year the firm would introduce little tweaks to make their system easier to use, but it was showing its age.
On May 25th, 2014, Belgium held elections for many EU and national parliament seats, as well as some local contests. The next day, election officials noticed some irregularities with votes cast in Brussels using Delacroy Europe’s program.
About 2000 votes had to be voided, as they contained multiple votes for the same office from different parties (called ‘lists’ in Belgium). It wasn’t enough to sway any one election, but the media had already caught wind of the potential voter fraud. Adrien’s company was hired for an independent code review of Delacroy Europe’s voting program to determine if anything criminal had transpired.
First, Adrien ruled out users manually rewriting data to the magnetic strip on the ballot card. It would require a credit card reader, which would have been difficult to smuggle into a polling place, and unlikely to happen on the scale of 2000 ballots or so. He suspected the problem was in the code.
He noticed something strange in the UI selection functions, triggered when the user selects a candidate on the viewscreen. Parliamentary ballots were organized by lists that a user picked from. Cand_Check_Selection()
determines if a candidate was checked, and if so, whether to select or unselect them (either Cand_Select()
or Cand_Unselect()
).
void Cand_Select(int _x, int _y, int _z)
{
arcMemoCandid[_x][_y][_z] = 1;
arcMemoList[_x][_y] = 1;
arcMemoScrutin[_x] = 1;
}
void Cand_Unselect(int _x, int _y, int _z)
{
arcMemoCandid[_x][_y][_z] = 0;
//280613 arcMemoList[_x][_y] = 0;
//280613 arcMemoScrutin[_x] = 0;
}
static int Cand_Check_Selection(int _iX, int _iY)
{
...
if((_iY>=y1) && (_iY<=y2)) /* This area */
{
if(arcMemoCandid[giCurrentScr][giCurrentList][i] == 0) /* Unselected -> Selected */
{
++giNbCandidSel; /* cfr iSWDeselectC */
Cand_Select(giCurrentScr,giCurrentList,i);
Cand_Update(i);
Cand_Circle(i);
Cand_Switch(i);
return(0);
}
#ifdef EL2014
else /* Selected -> Unselected */
{
--giNbCandidSel; /* cfr iSWDeselectC */
if (giNbCandidSel == 0) {
swpas = swnoir = swconf = 0;
}
Cand_Unselect(giCurrentScr,giCurrentList,i);
Cand_Update(i);
Cand_Circle(i);
Cand_Switch(i);
return(0);
}
#endif
}
...
}
He found two commented lines, dated June 28, 2013, a year before election day. A developer, looking at Card_Unselect()
, realized that by unselecting a candidate, it also unselected everyone in that candidate’s list. They commented out two lines, thinking they had fixed the error. However, the unselection algorithm never decremented the check counter, which kept track of how many candidates had been chosen. If a user checked a candidate on one list, changed their mind, and picked another from a separate list, then both votes would be counted.
It hadn’t been a case of fraud, but some poorly-placed comments.
So far, Brussels has not seen any further election scandals, but Adrien knows with this kind of code, it’s only a matter of time.