Навеяно вот этим постом. Люблю этот жанр, прочитал и вспомнил свои истории, захотелось поделиться. Не везде был виноват именно я, но в той или иной степени участвовал. Надеюсь, получилось интересное пятничное чтиво.
Так сложилось, что бо́льшую часть своей жизни я провел в финтэке, поэтому все истории связаны с банками и/или финансами, так что иногда удавалось нанести вполне реальный ущерб. Истории в хронологическом порядке, при этом самая "тяжелая" - последняя, что логично, с накоплением опыта инженер может нанести больше вреда. Истории эти старые, так что не исключено, что какие-то детали я забыл или переврал - это не специально.
1. О пользе валидации данных
В первой половине 90-х писал я реестр акционеров для довольного большого по тем временам холдинга. Народ активно нес денежки под обещание 60% годовых (они и правда были, не кидалово), а чтобы сделать это еще более привлекательным, была объявлена лотерея - обладатель акции с выигравшим номером получал значительную сумму, не помню точно сколько, но помню, что прилично, квартиру можно было купить.
В последние дни перед розыгрышем хлынул такой поток, что операторы, умевшие работать с системой, не справлялись, поэтому им на подмогу посадили всех, кто попался под руку, выделили каждому диапазон номеров, и люди просто записывали на бумажке, кому какие номера достались. Естественно, потом все эти данные нужно было ввести в систему, и потом еще пол-ночи операторы сидели и все это вбивали. Поскольку больше мы не могли обеспечить последовательность нумерации, мне пришлось по-быстрому отключить кое-какие валидации в форме ввода данных. Данные вводят люди, которые хорошо знают систему, что может пойти не так?
И вот уже когда финал был близок, оставалось ввести всего ничего и я уже мысленно собирался домой, на подгибающихся ногах входит одна из наших операторов и в состоянии, близком к истеричному, говорит, что она сделала что-то не то. Я бегу смотреть, и выясняется, что она случайно перепутала поля ввода начального номера и количества, то есть получалось, что клиент вместо пары акций купил десятки, если не сотни тысяч. По тому, как компьютер задумался, хотя раньше все срабатывало моментально, она поняла, что косяк, и сразу вырубила комп (да, логика была в клиенте, на сервере только данные), и успели перетереться данные по небольшому количеству акций, может пара тысяч (каждая акция - отдельная запись в БД, такая модель данных была), но надо было найти правильную информацию по бумажным записям и вбить все руками. Я сидел и диктовал оператору данные, она вбивала. Разошлись под утро. Никого не побили, хотя я имел, что слушать (и справедливо, конечно).
2. О пользе тестирования в боевой обстановке
Эта история произошла сразу после первой, на следующее же утро. Тут надо сделать маленькое отступление относительно архитектуры моего приложения - вся логика, как я уже говорил, была в клиенте, а данные хранились на сервере, в Btrieve, это такой ISAM, встроенный в NetWare, очень быстрый. У Btrieve было два варианта - для NetWare (сетевой) и для DOS (персональный), и важно отметить, что формат файлов данных был общий, так что план был такой - взять данные сервера, перекинуть на ноут, и там локально по ним искать, благо приложение умело работать и с локальным Btrieve'ом тоже (весь процесс был неоднократно протестирован). Уже догадались, что будет дальше?
Начиная с какой-то версии Novell бросил поддержку DOS-овской версии Betrieve, и незадолго до этого мы апгрейднули NetWare, ну и Btrieve с ним. Поэтому когда я увидел сообщение о поврежденном файле данных, я сразу понял, что при апгрейде Btrieve изменил формат файлов, и локально все это работать не может. Где-то на телевидении осветители уже прогревали софиты, чтобы показать всему миру розыгрыш счастливой акции, не зная о том, что розыгрыша не будет, потому что мы не сможем сходу найти, кому принадлежит выпавший номер. Я понял, что это мой последний рабочий день, сел на стол и закурил, прямо в офисе, задумчиво глядя в окно.
По моему нетипичному поведению окружающие поняли, что что-то нехорошо. И та самая оператор, которой я давеча пол-ночи диктовал данные по акционерам, сказала гениальное: "сделай отчет по всем акционерам с номерами акций в текстовый файл, и просто по текстовому файлу будем искать". Простое и очевидное решение, тем более что отчет такой уже написан, запустить его - минутное дело. И уже через 15 минут мы ехали на телевидение, где все прошло как по маслу, потому что поиск по текстовому файлу даже я не могу сломать так быстро.
3. О в̶р̶е̶д̶е̶пользе начальника с крепкой рукой
Несколько крупных финансовых организаций решили замутить валютный аукцион. Моя контора внесла свою долю софтом, который наш отдел разработал по требованиям, полученным от руководства. Система получилась любо-дорого - сетевая, многопользовательская, модные TurboVision'овские интерфейсы, данные опять же в Btrieve, клиенты кидают серверу уведомления по SPX, данные выводятся на бегущую строку, автоматически печатаются красивые подтверждения сделок (сколько мы бились с PCL5e!). И работало шустро. Для тех времен, когда почти все писалось на Clipper'е или FoxPro, это был космос.
Наступает день первого аукциона. Обрудование смонтировано, бегущая строка переливается цифрами, в принтер заложена дорогая бумага с водяными знаками - круто до невозможности. За полчаса до начала подходит большой босс и говорит, что руководители банков сейчас перетерли и решили поменять принцип проведения аукциона. Я не помню точно, в чем заключалось изменение, но главное - оно не ложилось в ту логику, которую я запрограммировал. Я сказал, что за полчаса до запуска ничего менять не буду, и уже было собрался сесть на стол и закурить, но тут почувствовал на плече крепкую руку своего непосредственного начальника, которой он вдавливал меня в стул, и услышал его злобный голос: "показывай код!". Я не знаю, зачем я взял с собой дискеты с исходниками и компилятором, наверно чутье. Я быстро проинсталлировал компилятор, открыл код, мы вместе с шефом посмотрели код, и решили, что самое простое и надежное будет добавить в одном месте goto. Да, goto. В программу на Паскале! Хорошо, что в этот момент рядом не было старика Вирта.
Уже догадались, что будет дальше? А вот и нет! Все отработало, как надо. Вот такой случился полный Agile в то время, когда еще и слова такого не было.
4. О том, что программисты могут в железо
Через какое-то время моя контора накрылась медным тазом, и я пошел работать в одну известную, в то время по большей части железячную контору, но занимался там поддержкой софта, опять же финансового.
И вот как-то раз мой коллега, который занимался банкоматным железом, рассказал мне про один проблемный банкомат, который постоянно теряет связь, но проблема явно не железная, так что мне надо ехать разбираться. Надо - значит надо, на следующее утро мы с ним сели в машину и поехали, 150 км до банкомата. Приезжаем - банкомат off-line. Вызываем инкассацию, ждем их, они забирают деньги и оставляют нам открытый банкомат. Я на всякий случай переустанавливаю весь софт, созваниваюсь с оператором X.25 сети, сверяем все коммуникационные параметры, все хорошо, связь есть. Ждем полчаса, час - связь есть. Вызываем инкассацию, они заряжают деньги, вводят ключи шифрования в банкомат, закрывают банкомат.
Мы выходим из отделения банка и садимся напротив перекусить. Пока ждем свой заказ, я замечаю, что картинка на экране банкомата перестает меняться. Подхожу ближе - точно, off-line. Звоню оператору - да, связь пропала с нашей стороны. Обед отменяется, возвращаемся в отделение, вызываем инкассацию, они забирают деньги, оставляют нам открытый банкомат. Через какое-то время связь магическим образом восстанавилась, сама! Но что поменялось? Не могут же кассеты с деньгами влиять, в самом деле! Может, дверь? Закрываю дверь - через пять минут связь пропадает. Открываю - восстанавливается. Уже что-то. Исследуем дверь - ничего она не пережимает, ну никак. На что еще может влиять закрытая дверь? Температура, влажность. Может, где-то плохой контакт, холодная пайка? Поскольку диагностика ни на что не жалуется, скорее всего проблемы уже где-то совсем на выходе, может, плохо припаяны провода к RS-232 разьему? Я дергаю за пучок проводов, и один провод отходит. Припаиваем провод, закрываем дверь, ждем - связь есть! Снова вызываем инкассацию, они уже конкретно недовольные приезжают, заряжают деньги и смотрят так злобно, достали мы их, а я не люблю доставать суровых дядек с оружием, так что обещаю им, что это в последний раз, и надеюсь, что это правда.
Мы решаем все же перекусить напротив, на этот раз нам это удается, по прошествии получаса банкомат все еще работает, и мы уезжаем.
P.S. Кстати, по поводу кассет - как-то раз я заходил к клиенту, и клиент попросил меня забрать пару сломанных кассет, отдать коллегам. Иду я по улице, несу кассеты, и слышу, как сзади пацаны мелкие переговариваются, и один другому говорит: "это кассеты для банкомата!". И тут до меня дошло, что я один иду по улице с кассетами, окружающие догадываются, что это кассеты, но не знают, что они пустые. А время было суровое, середина 90-х. Но ничего, дошел до офиса, и кассеты донес.
5. О том, что программисты не (всегда) могут в железо
Была такая карточная сеть - Europay, европейский оператор MasterCard, ну и свои карты у них тоже были. Для подключения к их сети использовался Евромодуль на базе IBM Series/1, очень древней мини-машины. Обслуживал этот Евромодуль с десяток крупных местных банков, так что основной поток транзакций по картам MasterCard/Eurocard/Maestro шел через него. И надо было в этом Евромодуле что-то поменять, кажется, диск поставить побольше, и сделать это надо было ночью, когда транзакций мало.
Почему-то менять диск послали меня, при том что у нас было три чистых железячника. Наверно, просто потому, что я был самый молодой, и мне было нелениво пойти в 3 часа ночи в датацентр. Пришел. Отключили Евромодуль, поменял я диск, включаю по инструкции (это вообще привет из 70-х - сначала включаешь диск, по звуку определяешь, что он раскрутился, потом включаешь системный блок) - на панели код ошибки. По справочнику ошибок смотрю - проблема с сетевым интерфейсом. Вытаскиваю карту "моего любимого" X.25, засовываю назад - Евромодуль начинает выкидывать ошибку, что вообще карта не обнаруживается. Пару часов я еще помучился, пока из банков не стали звонить и спрашивать, что происходит. Тогда чуваки из датацентра сказали, чтобы я шел на фиг, они сами будут разбираться. Как я понял, они заказали в Europay новую карту, и им ее в тот же день прислали, но менял ее уже не я. В любом случае весь день транзакции по картам не проходили.
Я не знаю, как я ухитрился сломать эту карту, потому что я все делал очень аккуратно, и даже надел антистатический браслет, как взрослый. До сих пор слабо надеюсь, что это просто так совпало неудачно.
6. О том, что никогда не знаешь, откуда может прилететь
С распространением карт и банкоматов попер фрод, это неизбежно. И вот как-то раз банк A поймал кардера, который в их банкомате снимал деньги. Бывает. Пикантности этой истории добавило то, что им оказался работник карточного отдела банка Б, человек, который в этом самом банке Б заведовал банкоматами.
Мне потом удалось по крупицам восстановить картину, и выглядела схема примерно так - этот работник занимался установкой банкоматов, в том числе вводил банкоматные ключи, ну и как-то раз у него остались в руках оба компонента ключа. Поскольку он занимался банкоматами, он то ли в логах (это было задолго до введения PCI DSS, запрещавшего сохранять PIN-блоки хоть в каком виде), то ли просто сниффером читал весь банкоматный трафик, там он смог найти транспортный ключ, и им уже расшифровать PIN-блоки из того же трафика. Ну и содержимое магнитной полосы, самой собой. Дальше он нашлепал несколько карт и отправился к банкомату банка А, прямо на той же самой улице, так что не исключаю, что его просто по камерам отследили - они в том районе густо понатыканы.
Как эта история касалась меня? К сожалению, напрямую. Банк Б был моим клиентом, и я этого парня обучал, рассказывал ему про протокол Diebold 912 (протоколов общения банкоматов с контроллером, который они использовали), про возможные атаки и способы их предотвращения, так что вполне возможно, что идею он почерпнул из моих рассказов и документации, которую я ему давал. Потом у меня был долгий разговор с руководителем ИТ банка - он хотел выяснить, не был ли я причастен к этой истории. Было очень неприятно.
7. О пользе внимательности
Потом я работал в другой конторе, там мы писали софт для карточного процессинга и для управления банкоматами. И был у нас TCP/IP-шный драйвер, который использовался для связи с банкоматами, и был он глюковат. Сети в те времена были ненадежные, связь часто терялась, при этом терялась плохо, без посылки RST, и получали мы классический half-open.
Чтобы обрабатывать такие ситуации, драйвер после accept'а опять запускал listener (по некоторым причинам первый listener мы закрывали), и когда приходил новый коннект, проверял IP адрес - если это банкомат, то старый сокет закрывался, и использовался новый. В теории должно работать, и на всех юнит-тестах работало, как надо. Но в составе системы - не работало. Я взялся разобраться с этой проблемой, и помню, что наворотил неслабо, фактически продублировал работу listen queue в приложении (потому что в kernel'е она, похоже, не работала так, как нам надо), и на каждый слот этой очереди выделялась структура в памяти. В тестовой среде с одним банкоматом все замечательно отработало. Ставим драйвер клиенту, и моментально от клиента прилетают матюги - каждый драйвер сжирает около 3 мегов памяти, а драверов запускается по количеству банкоматов, то есть сотни. На дворе был 2000 год или около того, то есть нормальный объем памяти у приличного HP 9000 L-класса был в районе 1-2 гигов, а тут 200 банкоматов сразу отожрали 600 мегов. Сел разбираться, и оказалось, что банкоматная подсистема, когда общается с TCP/IP-драйвером, передает параметры в неправильном порядке, и значение таймаута в 10000 миллисекунд попадает в параметр длины очереди, и драйвер выделяет память под 10000 структур, как просили. И разрешилась загадка с оригинальным драйвером - когда он использовал такой огромный размер очереди, HP'шный kernel не возвращал ошибку, но и входящие коннекты не принимал.
То есть если бы, вместо того чтобы бросаться решать задачу, я внимательно почитал код и поменял местами две константы в вызове функции, драйвер вообще не пришлось бы переписывать. Правда, в новом драйвере решалось еще несколько мелких проблем, так что мы его оставили, но порядок параметров исправили.
8. О пользе проверки размеров буфера
Через какое-то время я работал уже в другом месте, и не занимался банкоматами. В какой-то день вдруг падает банкоматная сеть одного крупного банка, и в магазинах их карты не принимаются. Ну, как водится, мы поржали над программистами-рукожопами, и забыли. Через пару часов все снова заработало.
А еще через пару часов мне звонит бывший коллега, и рассказывает историю. Но эта история требует предыстории.
Дело в том, что номер карты состоит из 6-значного префикса (он называется BIN), 9 произвольных цифр и одного контрольного разряда. BINы стоят денег, поэтому их стараются использовать экономно, и несколько первых цифр из 9 произвольных используют как код продукта (разные продукты могут иметь разный набор разрешенных операций, лимиты, комиссии, да даже авторизовываться по-разному), таким образом обычно выделяют карточный префикс, который состоит из BIN'а и 2-3-значного кода продукта, оставляя 6-7 цифр произвольных. По этому префиксу карточная система идентифицирует продукт, и определяет, как обрабатывать транзакцию с этой картой, и делает это некий внутренний рутер.
Так вот, как рассказал мне бывший коллега, тот, кто писал этот рутер, выделил массив в 9 байт под карточный префикс, на тот случай, если банк настроит для BINа 3-значный код продукта, и все замечательно работало. А потом в какой-то момент банк для нового BINа задал 4-значный код продукта, то есть префикс стал 10 знаков, и в буфер уже не влезал. А проверку тот ленивый программист не сделал, потом что ну кому же придет в голову делать такой длинный код продукта! При первой же транзакции с картой с новым длинным префиксом рутер крэшнулся, а без него ничего не работает вообще.
Ну, вы поняли, кто был тем рукожопом. Мне было очень стыдно. Пожалуй, лучший момент для совершения сеппуку, будь я японцем.
Историй на самом деле больше, но я с самого начала ограничил себя круглой цифрой 8, и менее интересные истории отсеял. С момента последней истории прошло уже лет 20, так что наверно я все же чему-то научился, раз нет свежака. Или просто жизнь стала более скучная.