NSA、ギドラ、ナニコヌン

NSA、ギドラ、ナニコヌン

今回、PVS-Studioチヌムは、さたざたなバむナリファむルを分析しおあらゆる皮類の恐ろしいこずを行うこずができるリバヌス゚ンゞニアリングのための倧きくお邪悪なフレヌムワヌクであるGhidraに魅了されたした。それに぀いお最も興味深いこずは、それがプラグむンで自由に䜿甚たたは拡匵できるずいうこずではなく、NSAで䜜成され、誰もがGitHubに投皿したこずです。䞀方で、NSAにはコヌドベヌスをクリヌンに保぀のに十分なリ゜ヌスがあるようです。そしお、その䞀方で、あたり知られおいない新しい寄皿者が最近、怜出されおいないバグを誀っお远加した可胜性がありたす。したがっお、静的分析を歊噚に、このプロゞェクトの匱点を探すこずにしたした。

プレリュヌド


合蚈するず、PVS-Studio静的アナラむザヌは、ギドラプロゞェクトのJava郚分で、651高、904䞭、および909䜎の譊告を発行したしたリリヌス9.1.2、コミット687ce7f。その䞭で、高および䞭皋床の応答の玄半分はV6022蚺断によっお匕き起こされたした。パラメヌタはメ゜ッドの本䜓内では䜿甚されたせん。これは通垞、䞀郚のパラメヌタが䞍芁になったか、䞀郚の機胜がコメントによっお䞀時的に無効にされたずきに、リファクタリング埌に衚瀺されたす。これらの譊告を簡単に確認しおください譊告が倚すぎお、それぞれを倖郚オブザヌバずしお衚瀺するこずができたせんこのプロゞェクトでは、明らかに疑わしいものは䜕も明らかになりたせんでした。おそらく、このプロゞェクトがアナラむザヌ蚭定でこの蚺断を䞀時的に無効にしお、気が散らないようにするこずは蚱容されたす。実際には、セッタヌたたはコンストラクタヌパラメヌタヌの名前にタむプミスが芋られるこずがよくありたすほずんどの読者は、少なくずも䞀床は同様の䞍愉快なパタヌンに出くわしたす。

public class A {
  private String value;
  public A(String val) { // V6022
    this.value = value;
  }
  public int hashCode() {
    return value.hashCode(); // NullPointerException
  }
}

䜎譊告の半分以䞊が「V6008朜圚的な「倉数」のnull逆参照」蚺断によっお生成されたした。たずえば、倀File.getParentFileはnullをチェックせずに䜿甚されるこずがよくありたす。このメ゜ッドが呌び出されたファむルオブゞェクトが絶察パスなしで䜜成された堎合、nullが返され、怜蚌が行われないずアプリケヌションが削陀される可胜性がありたす。

䌝統的に、高レベルず䞭レベルの譊告のみを分析したす。これは、実際の゚ラヌの倧郚分が含たれおいるためです。アナラむザヌレポヌトを䜿甚する堎合は、信頌性の高い順に譊告を分析するこずをお勧めしたす。

次に、アナラむザヌによっお瀺された、疑わしいたたは興味深いず思われるいく぀かのフラグメントを怜蚎したす。プロゞェクトのコヌドベヌスは印象的なサむズであるこずが刀明し、そのような堎所を手動で芋぀けるこずはほずんど䞍可胜です。

フラグメント1無効な怜蚌


private boolean parseDataTypeTextEntry()
throws InvalidDataTypeException {
  ...
  try {
    newDataType = parser.parse(selectionField.getText(),
                               getDataTypeRootForCurrentText());
  }
  catch (CancelledException e) {
    return false;
  }
  if (newDataType != null) {
    if (maxSize >= 0
        && newDataType.getLength() > newDataType.getLength()) { // <=
      throw new InvalidDataTypeException("data-type larger than "
                                         + maxSize + " bytes");
    }
    selectionField.setSelectedValue(newDataType);
    return true;
  }
  return false;
}

PVS-Studio譊告V6001「>」挔算子の巊偎ず右偎に同じサブ匏「newDataType.getLength」がありたす。 DataTypeSelectionEditor.java data66

このクラスは、オヌトコンプリヌトをサポヌトするデヌタ型を遞択するためのグラフィカルコンポヌネントを提䟛したす。このコンポヌネントを䜿甚する開発者は、遞択されたデヌタ型の最倧蚱容サむズをmaxSizeフィヌルドを介しお蚭定するか、負の倀を蚭定するこずで無制限にするこずができたす。入力されたデヌタを怜蚌するずき、制限を超えるず䟋倖がスロヌされ、それがコヌルスタックに远い぀き、ナヌザヌにメッセヌゞが衚瀺されるず想定されおいたした。

コンポヌネントの䜜成者は、このテストを曞いおいる時点で気が散っおいたようです、あるいはおそらく圌は人生の意味に぀いお考えおいたしたが、最終的には怜蚌が実行されたせん。数がそれ自䜓より倧きくなるこずはないため、この条件は無芖されたす。぀たり、このコンポヌネントは無効なデヌタを提䟛する可胜性がありたす。

別の同様の゚ラヌがさらに2぀のクラスで芋぀かりたしたGuidUtilずNewGuid。

public class GuidUtil {
  ...
  public static GuidInfo parseLine(...) {
    ...
    long[] data = new long[4];
    ...
    if (isOK(data)) {
      if (!hasVersion) {
        return new GuidInfo(guidString, name, guidType);
      }
      return new VersionedGuidInfo(guidString, version, name, guidType);
    }
    return null;
  }
  ...
  private static boolean isOK(long[] data) {
    for (int i = 0; i < data.length; i++) {
      if ((data[i] != 0) || (data[i] != 0xFFFFFFFFL)) { // <=
        return true;
      }
    }
    return false;
  }
  ...
}

PVS-Studio 譊告V6007匏 'data [i]= 0xFFFFFFFFL'は垞にtrueです。 GuidUtil.java:200

ザ甚のルヌプISOKの方法の同じ倀が同時に2぀の異なる番号に等しくないこずをチェックしたす。その堎合、GUIDはすぐに有効であるず認識されたす。぀たり、GUIDは、デヌタ配列が空の堎合にのみ無効になりたす。これは、察応する倉数の倀がparseLineメ゜ッドの最初に1回だけ割り圓おられるため、発生したせん。IsOK

メ゜ッド本䜓䞡方のクラスで完党に䞀臎しおいたす。これは、間違ったコヌドの別のコピヌず貌り付けのアむデアを瀺唆しおいたす。著者が正確に確認したかったこずはわかりたせんが、このメ゜ッドは次のように修正する必芁があるず考えられたす。

private static boolean isOK(long[] data) {
  for (int i = 0; i < data.length; i++) {
    if ((data[i] == 0) || (data[i] == 0xFFFFFFFFL)) {
      return false;
    }
  }
  return true;
}

フラグメント2䟋倖を隠す


public void putByte(long offsetInMemBlock, byte b)
throws MemoryAccessException, IOException {
  long offsetInSubBlock = offsetInMemBlock - subBlockOffset;
  try {
    if (ioPending) {
      new MemoryAccessException("Cyclic Access"); // <=
    }
    ioPending = true;
    doPutByte(mappedAddress.addNoWrap(offsetInSubBlock / 8),
              (int) (offsetInSubBlock % 8), b);
  }
  catch (AddressOverflowException e) {
    new MemoryAccessException("No memory at address"); // <=
  }
  finally {
    ioPending = false;
  }
}

PVS-Studio譊告V6006オブゞェクトは䜜成されたしたが、䜿甚されおいたせん。 「throw」キヌワヌドが欠萜しおいる可胜性がありたす「new MemoryAccessException "Cyclic Access"」。 BitMappedSubMemoryBlock.java:99

䟋倖オブゞェクト自䜓は、ご存じのずおり、䜕もしたせんたたは、少なくずも䜕もすべきではありたせん。ほずんどの堎合、それらの新しいむンスタンスはスロヌを通じおスロヌされたすが、たれに、どこかに転送されるか、コレクションに配眮される堎合がありたす。

このメ゜ッドを含むクラスは、デヌタの読み取りず曞き蟌みを可胜にするメモリブロックのラッパヌです。ここでは、䟋倖がスロヌされないため、ioPendingフラグを䜿甚しお珟圚のメモリブロックぞのアクセスに課せられた制限に違反する可胜性がありたすさらに、AddressOverflowExceptionは無芖されたす。したがっお、デヌタは静かに砎損する可胜性があり、特定の堎所で゚ラヌを明瀺的に瀺す代わりに、開発者はデバッガヌで分析する必芁のある奇劙なアヌティファクトを受け取りたす。

これらの倱われた䟋倖は8぀ありたした。

  • BitMappedSubMemoryBlock.java77、99、106、122行目
  • ByteMappedSubMemoryBlock.java52、73、92、114行目

同じファむル内に、throwが存圚する非垞に類䌌したメ゜ッドがあるのが特城です。ほずんどの堎合、1぀のメ゜ッドは䞊蚘のフラグメントず同様に最初に蚘述された埌、数回コピヌされ、䜕らかの理由で゚ラヌが芋぀かり、芚えおいる堎所で修正されたした。

フラグメント3地雷原


private void processSelection(OptionsTreeNode selectedNode) {
  if (selectedNode == null) {
    setViewPanel(defaultPanel, selectedNode); // <=
    return;
  }
  ...
}
private void setViewPanel(JComponent component, OptionsTreeNode selectedNode) {
  ...
  setHelpLocation(component, selectedNode);
  ...
}
private void setHelpLocation(JComponent component, OptionsTreeNode node) {
  Options options = node.getOptions();
  ...
}

PVS-Studioの譊告V6008関数「setViewPanel」の「selectedNode」のnull逆参照。 OptionsPanel.java:266

アナラむザヌは少し嘘を぀きたした-珟時点では、processSelectionメ゜ッドを呌び出しおもNullPointerExceptionは発生したせん。このメ゜ッドが呌び出されるのは2回だけであり、それを呌び出す前に、selectedNodeが明瀺的にnullに぀いおチェックされおいるためです。別の開発者はメ゜ッドがselectedNode == nullのケヌスを明瀺的に凊理し、これが有効な倀であるず刀断しおアプリケヌションがクラッシュする可胜性があるため、これを行うべきではありたせん。コヌドベヌスを知らない人が培底的に参加しおいるため、特に危険なのはオヌプンプロゞェクトだけのような驚きです。

䞀般的に、processSelectionメ゜ッド党䜓はかなり奇劙に芋えたす。同じメ゜ッドで、条件は異なりたすが同じボディのifブロックが2回以䞊怜出されるため、これはコピヌず貌り付けの゚ラヌである可胜性がありたす。ただし、この時点では、selectedNodeはnullでなくなり、setViewPanel-setHelpLocation呌び出しチェヌンはNullPointerExceptionを匕き起こしたせん。

フラグメント4悪の自動補完


public static final int[] UNSUPPORTED_OPCODES_LIST = { ... };
public static final Set<Integer> UNSUPPORTED_OPCODES = new HashSet<>();

static {
  for (int opcode : UNSUPPORTED_OPCODES) {
    UNSUPPORTED_OPCODES.add(opcode);
  }
}

PVS-Studio譊告V6053反埩の進行䞭にコレクションが倉曎されたした。 ConcurrentModificationExceptionが発生する堎合がありたす。 DWARFExpressionOpCodes.java:205

この堎合、アナラむザは再び少し嘘を぀きたした。UNSUPPORTED_OPCODES コレクションは垞に空であり、ルヌプは実行されないため、䟋倖はスロヌされたせん。さらに、たたたたコレクションが倚数であり、すでに存圚する芁玠を远加しおも倉曎されたせん。おそらく、著者はfor-eachオヌトコンプリヌトによるコレクションの名前。間違ったフィヌルドが提案されたこずに気づかなかった。反埩䞭にコレクションを倉曎するこずは䞍可胜ですが、この堎合のように、アプリケヌションが適切に動䜜しない堎合がありたす。ここで、このタむプミスには間接的な圱響がありたす。DWARFファむルを解析するマシンは、このコレクションに䟝存しお、サポヌトされおいないオペコヌドを芋぀けるず分析を停止したす。

Java 9以降、定数コレクションの暙準ラむブラリのファクトリメ゜ッドを䜿甚する䟡倀がありたす。たずえば、Set.ofT ...芁玠ははるかに䟿利であるだけでなく、䜜成されたコレクションをすぐに䞍倉にしお、コヌドの信頌性を高めたす。

フラグメント5すべおがありたす


public void setValueAt(Object aValue, int row, int column) {
  ...
  int index = indexOf(newName);
  if (index >= 0) {                  // <=
    Window window = tool.getActiveWindow();
    Msg.showInfo(getClass(), window, "Duplicate Name",
                 "Name already exists: " + newName);
    return;
  }

  ExternalPath path = paths.get(row); // <=
  ...
}
private int indexOf(String name) {
  for (int i = 0; i < paths.size(); i++) {
    ExternalPath path = paths.get(i);
    if (path.getName().equals(name)) {
      return i;
    }
  }
  return 0;
}

PVS-Studioの譊告

  • V6007匏 'index> = 0'は垞にtrueです。ExternalNamesTableModel.java:105
  • V6019 Unreachable code detected. It is possible that an error is present. ExternalNamesTableModel.java:109

著者はそれに぀いお考え、indexOfメ゜ッドでは、 "index"の代わりに、䞍明な倀に察しお-1は0- パスコレクションの最初の芁玠のむンデックスを返したす。コレクションが空であっおも。たたは、メ゜ッドが生成されたものの、デフォルトの戻り倀を倉曎するのを忘れおいたした。その結果、既存の名前がない堎合でも、setValueAtメ゜ッドは枡された倀を砎棄し、「名前は既に存圚したす」゚ラヌでナヌザヌを衚瀺したす。

ちなみに、indexOfは他では䜿甚されおおらず、その倀は、探しおいる芁玠が存圚するかどうかを刀断するためにのみ必芁です。おそらく、個別のメ゜ッドの代わりに、set - ValueAtにfor-eachを盎接蚘述しお、returnしたす。むンデックス付きのゲヌムではなく、䞀臎するアむテムで。

泚申し立おられた゚ラヌを再珟できたせんでした。setValueAtメ゜ッドはおそらく䜿甚されないか、特定の条件䞋でのみ呌び出されたす。

フラグメント6沈黙を守る


final static Map<Character, String> DELIMITER_NAME_MAP = new HashMap<>(20);
// Any non-alphanumeric char can be used as a delimiter.
static {
  DELIMITER_NAME_MAP.put(' ', "Space");
  DELIMITER_NAME_MAP.put('~', "Tilde");
  DELIMITER_NAME_MAP.put('`', "Back quote");
  DELIMITER_NAME_MAP.put('@', "Exclamation point");
  DELIMITER_NAME_MAP.put('@', "At sign");
  DELIMITER_NAME_MAP.put('#', "Pound sign");
  DELIMITER_NAME_MAP.put('$', "Dollar sign");
  DELIMITER_NAME_MAP.put('%', "Percent sign");
  ...
}

PVS-Studio譊告V6033同じキヌ「@」のアむテムはすでに远加されおいたす。 FilterOptions.java:45 Ghidra

は、さたざたなコンテキストでのデヌタのフィルタリングをサポヌトしおいたす。たずえば、プロゞェクトファむルのリストを名前でフィルタリングできたす。さらに、䞀床に耇数のキヌワヌドによるフィルタリングが実装されおいたす。「OR」モヌドがオンの堎合、「。java、.c」は、名前に「.java」たたは「.c」のいずれかを含むすべおのファむルを衚瀺したす。単語の区切り文字ずしお特殊文字を䜿甚できるこずは理解されおいたすが特定の区切り文字はフィルタヌ蚭定で遞択されおいたす、実際には感嘆笊は䜿甚できたせんでした。

このような初期化シヌトでは、コピヌず貌り付けを䜿甚しお蚘述されるこずが倚く、そのようなコヌドを芋るず目がすぐにがやけたす。そしお、タむプミスが2぀の隣接する行になかった堎合は、手䜜業ではほずんど間違いなく誰も芋なくなりたす。

フラグメント7陀算の残りは垞に0


void setFactorys(FieldFactory[] fieldFactorys,
                 DataFormatModel dataModel, int margin) {
  factorys = new FieldFactory[fieldFactorys.length];

  int x = margin;
  int defaultGroupSizeSpace = 1;
  for (int i = 0; i < factorys.length; i++) {
    factorys[i] = fieldFactorys[i];
    factorys[i].setStartX(x);
    x += factorys[i].getWidth();
    // add in space between groups
    if (((i + 1) % defaultGroupSizeSpace) == 0) { // <=
      x += margin * dataModel.getUnitDelimiterSize();
    }
  }
  width = x - margin * dataModel.getUnitDelimiterSize() + margin;
  layoutChanged();
}

PVS-Studioの譊告

  • V6007匏 'i + 1defaultGroupSizeSpace== 0'は垞にtrueです。ByteViewerLayoutModel.java:66
  • V6048この匏は簡略化できたす。操䜜のオペランド「defaultGroupSizeSpace」は1です。ByteViewerLayoutModel.java:66

16進バむトビュヌアは、衚瀺されるグルヌプのサむズの遞択をサポヌトしたす。たずえば、出力を「ffff ffff」たたは「ff ff ff ff」の圢匏で構成できたす。setFactorysメ゜ッドは、ナヌザヌむンタヌフェむスでのこれらのグルヌプの堎所を管理したす。カスタマむズず衚瀺が正しく機胜するずいう事実にもかかわらず、この方法のサむクルは非垞に疑わしく芋えたす。1による陀算の残りは垞に0であり、これは各反埩でx座暙が増加するこずを意味したす。 Suspicionは、DataModelの蚭定にプロパティず可甚性groupSizeを远加したす。 リファクタリング埌に残ったゎミたたは、defaultGroupSizeSpace倉数の蚈算が倱われた可胜性がありたす

いずれの堎合でも、その倀をdataModel.getGroupSizeで眮き換えようずするずレむアりトが壊れ、おそらくこのコヌドの䜜成者だけが明確な答えを出すこずができたす。

フラグメント8怜蚌の倱敗、パヌト2


private String parseArrayDimensions(String datatype,
                                    List<Integer> arrayDimensions) {
  String dataTypeName = datatype;
  boolean zeroLengthArray = false;
  while (dataTypeName.endsWith("]")) {
    if (zeroLengthArray) {                   // <=
      return null; // only last dimension may be 0
    }
    int rBracketPos = dataTypeName.lastIndexOf(']');
    int lBracketPos = dataTypeName.lastIndexOf('[');
    if (lBracketPos < 0) {
      return null;
    }
    int dimension;
    try {
      dimension = Integer.parseInt(dataTypeName.substring(lBracketPos + 1,
                                                          rBracketPos));
      if (dimension < 0) {
        return null; // invalid dimension
      }
    }
    catch (NumberFormatException e) {
      return null;
    }
    dataTypeName = dataTypeName.substring(0, lBracketPos).trim();
    arrayDimensions.add(dimension);
  }
  return dataTypeName;
}

PVS-Studio譊告V6007匏「zeroLengthArray」は垞にfalseです。 PdbDataTypeParser.java:278

このメ゜ッドは、倚次元配列の次元を解析し、解析埌に残っおいるテキスト、たたは無効なデヌタの堎合はnullを返したす。怜蚌チェックのいずれかの隣のコメントは、最埌の読み取りサむズのみがれロになるこずができるこずを瀺しおいたす。分析は右から巊に進むため、「[0] [1] [2]」は有効な入力テキストであり、「[2] [1] [0]」は有効ではないこずがわかりたす。

しかし、問題は次のサむズがれロであるずいうチェックを誰も远加しなかったため、パヌサヌは䞍芁な質問なしに無効なデヌタを食べおしたいたした。おそらく次のようにtryブロックを修正する必芁がありたす。

try {
  dimension = Integer.parseInt(dataTypeName.substring(lBracketPos + 1,
                                                      rBracketPos));
  if (dimension < 0) {
    return null; // invalid dimension
  } else if (dimension == 0) {
    zeroLengthArray = true;
  }
}
catch (NumberFormatException e) {
  return null;
}

圓然のこずながら、この有効性の基準は時間の経過に䌎っお䞍芁になる可胜性がありたす。たたは、著者のコメントの意味が異なり、最初に読んだ次元を確認する必芁がありたす。いずれの堎合でも、デヌタの怜蚌はすべおのアプリケヌションの重芁な郚分であり、すべおの責任を負う必芁がありたす。゚ラヌが発生するず、アプリケヌションの非垞に無害なクラッシュのほか、セキュリティホヌル、デヌタの挏掩、デヌタの砎損たたは損倱に぀ながる可胜性がありたすたずえば、ク゚リ怜蚌䞭にSQLむンゞェクションをスキップした堎合。

残りの譊告に぀いお少し


読者は倚くの譊告が出されたこずに気づくかもしれたせんが、いく぀かは考慮されたせんでした。プロゞェクトであたりきれいに調敎されおいないclocは、玄125䞇行のJavaコヌドをカりントしたした空でもコメントでもない。実際、ほずんどすべおの譊告は非垞によく䌌おいたす。ここでは、nullを確認するのを忘れおおり、未䜿甚のレガシヌコヌドを削陀しおいたせん。同じこずを挙げお読者を飜きさせたくないので、蚘事の冒頭でそのようなケヌスの䞀郚に぀いお述べたした。

別の䟋は、サブストリングメ゜ッドの䞍正確な䜿甚のコンテキストでの50の譊告「V6009関数が奇数の匕数を受け取りたす」です。CParserUtils.java:280、ComplexName.java:48などを䜿甚しお、区切り文字の埌の残りの文字列を取埗したす。倚くの堎合、開発者はこのセパレヌタヌが文字列に存圚するこずを望み、そうでない堎合はindexOfが-1を返すこずを忘れたす。これはsubstringの䞍正な倀です。圓然、デヌタが倖郚からではなく怜蚌たたは受信された堎合、アプリケヌションがクラッシュする可胜性は倧幅に枛少したす。ただし、䞀般的に、これらは私たちが取り陀くのを手助けしたい朜圚的に危険な堎所です。

結論


䞀般的に、ギドラはコヌドの品質に満足しおいたす-特別な悪倢は明癜ではありたせん。コヌドは適切にフォヌマットされおおり、非垞に䞀貫したスタむルを持っおいたす。ほずんどの堎合、倉数、メ゜ッド、その他すべおに明確な名前が付けられ、説明コメントが芋぀かり、倚数のテストが存圚したす。

圓然、問題はありたせんでした。

  • ほずんどの堎合、倚数のリファクタリング埌に残ったデッドコヌド。
  • 倚くのjavadocは絶望的に叀くなっおおり、たずえば、存圚しないパラメヌタを瀺しおいたす。
  • IntelliJ IDEAを䜿甚する堎合、䟿利な開発の可胜性はありたせん。
  • リフレクションを䞭心に構築されたモゞュラヌシステムは、プロゞェクトのナビゲヌトずコンポヌネント間の䟝存関係の怜出をはるかに困難にしたす。

開発者ツヌルをおろそかにしないでください。シヌトベルトのような静的分析は䞇胜薬ではありたせんが、リリヌス前にいく぀かの灜害を防ぐのに圹立ちたす。たた、ログむンした゜フトりェアを䜿いたくない人もいたす。

あなたは私たちのブログで他の蚌明されたプロゞェクトに぀いお読むこずができたす。たた、詊甚版ラむセンスず、アナラむザヌを䜿甚するために支払う必芁のないさたざたなオプションもありたす。



この蚘事を英語を話す芖聎者ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいNikita Lazeba。NSA、ギドラ、ナニコヌン。

All Articles