Thanks for the suggestion.
I did some more testing and noticed that changing the default role to something other than student, then running the sync script did not change their additional role... so that was not the case. We also double checked and made sure there was only one entry per person in the database, which there is.
So.. it had to be something else.
In DEV, I removed all users listed as faculty from the external user database, then ran the sync script. As expected, it removed all faculty from their courses. Then, I re-added all faculty back to the user database and ran the sync script again... it re-added them just as faculty this time--without the additional student role!
This makes me wonder--it may be possible I ran the sync script at a time when people who should have been faculty didn't actually have a role assigned to them in the external database. Doing so would have added them as the default "Student" role.
Then, when they were properly labeled as faculty in the database, the script ran again (it's on cron) and ALSO enrolled them as Faculty. But every time the script ran after that, it never checked to see if it should remove their Student role (which it should have since they no longer had an unassigned role in the external database).
This is all a guess as to what happened, but it's the best one I've got. I can't think of any other plausible reasons the external DB enrollment would act this way. If the code indeed does work this way (maybe someone better versed in PHP can check?) then I think we should open a new tracker ticket to improve the external db enrollment script's role-checking capabilities.