PVS-Studio release for macOS: 64 weaknesses on Apple XNU Kernel

    Bug in appleThe new version of PVS-Studio 6.23 runs on macOS and allows you to check projects written in C and C ++. Our team decided to coincide with the XNU Kernel check for this event.

    PVS-Studio for macOS


    With the release of the analyzer version for macOS, PVS-Studio can be safely called a cross-platform static code analyzer for C and C ++.

    Initially, there was a version for Windows only. About two years ago, our team supported Linux: " How PVS-Studio was created under Linux ." Also, an attentive reader of our blog should recall the articles about checking FreeBSD Kernel ( 1st article , 2nd article ). Then the analyzer was assembled to run in PC-BSD and TrueOS. And finally, we got to macOS!

    Is cross-platform product easy to develop?

    This issue has an economic and technical component.

    From an economic point of view, making the analyzer cross-platform was the right decision. Software development has been moving in this direction for a long time, and the tool for developers of such projects should be appropriate. However, if something is useful, this does not mean that it is immediately worth starting to do. At the beginning, we always make sure that we have enough strength to realize something in a new direction, and then support it.

    On the technical side, it is difficult only at the very beginning, if the project is not immediately conceived as cross-platform. We spent several months adapting the analyzer to the Linux system. Compiling the project for the new platform did not take much time: we do not have a graphical interface and the code is practically not tied to the use of system APIs. Most of the time was spent adapting the analyzer to new compilers and improving the quality of analysis. In other words, a lot of effort requires the prevention of false positives .

    What about macOS development?

    At this point, we already had a project analyzer file for CMake, easily adaptable to different operating systems. Testing systems of various types were also cross-platform. All this helped to launch very quickly on macOS.

    An analyzer development feature for macOS was the Apple LLVM Compiler. Although the analyzer was built using GCC and worked perfectly, it could still affect the compatibility of the analyzer with users' computers in the future. In order not to create problems for potential users, we decided to support the distribution build using this compiler, which comes with Xcode.

    The development of C ++ greatly helps in the creation and development of cross-platform projects, but different compilers add such features very unevenly, so conditional compilation is still actively used in several places.

    In general, everything went smoothly and easily. As before, most of the time was spent on finalizing exceptions, modifying the site, testing and other related issues. As the first project tested with PVS-Studio for macOS, we present you XNU Kernel .

    Distribution

    You can familiarize yourself with the methods of downloading and installing PVS-Studio for macOS here .

    XNU Kernel


    Where to start demonstrating the capabilities of PVS-Studio for macOS, if not by checking the kernel of this system! Therefore, the first project tested using the new version of the analyzer was XNU Kernel.

    XNU is the core of computer operating systems developed by Apple and used in the OS X family of OS (macOS, iOS, tvOS, watchOS). More details .

    It is believed that the kernel is written in C and C ++, but in fact it is C. I counted 1302 * .c files and only 97 * .cpp files. The size of the code base is 1929 KLOC. It turns out that this is a relatively small project. For comparison, the code base of the Chromium project is 15 times larger and contains 30 MLOC.

    The source code can be conveniently downloaded from the mirror on the GitHub website: xnu .

    Validation Results


    Although the XNU Kernel project is relatively small, studying the analyzer warnings alone is a big task that takes a lot of time. Complicating the study of warnings and false alarms is difficult, since I did not pre-configure the analyzer. I just quickly ran through the warnings, writing out code fragments of interest, in my opinion. This is more than enough to write an article, moreover, solid in size. PVS-Studio analyzer easily finds a large number of interesting errors.

    Note to XNU Kernel Developers. I had no problem finding as many errors as possible. Therefore, you should not be guided by this article to correct them. Firstly, this is inconvenient, since there is no way to navigate through alerts. I am sure it is much better to use one of the formats that PVS-Studio can generate, for example, an HTML report with the ability to navigate (it is similar to what Clang can generate). Secondly, I missed many mistakes simply because I studied the report superficially. I recommend that developers independently perform a more thorough analysis of the project using PVS-Studio.

    As I said, false positives prevented me, but in fact they are not a problem. If you configure the analyzer, then it is quite possible to reduce the number of false positives by 10-15%. Since it also takes time to set up and restarting the analysis process, I skipped this step - it’s easy for me to collect errors for the article without it. If you plan to carry out the analysis carefully, then, of course, you should take the time to configure.

    Mostly false positives arise due to macros and insufficiently well-marked functions. For example, in XNU Kernel, most of them are associated with the use of the panic function .

    This is how this function is declared:

    extern void panic(const char *string, ...)
      __attribute__((__format__ (__printf__, 1, 2)));

    The function is annotated so that its input arguments are interpreted by analogy with the arguments to the printf function . This allows compilers and analyzers to find errors in incorrect formatting of strings. However, the function is not marked as not returning control. As a result, the following code leads to false positives:

    if (!ptr)
      panic("zzzzzz");
    memcpy(ptr, src, n);

    Here the analyzer gives a warning that it is possible to dereference a null pointer. From his point of view, after calling the panic function, the memcpy function will be called .

    To avoid such false positives, you should change the function annotation by adding __attribute __ ((noreturn)) :

    extern __attribute__((noreturn)) void panic(const char *string, ...)
      __attribute__((__format__ (__printf__, 1, 2)));

    Let's now see what interesting things I managed to notice in the XNU Kernel code. In total, I wrote out 64 errors and decided to dwell on this beautiful number. I grouped defects according to the Common Weakness Enumeration , since many are familiar with this classification, and it will be easier for them to understand what errors are discussed in a particular chapter.

    CWE-570 / CWE-571: Expression is Always False / True


    Very varied errors can lead to CWE-570 / CWE-571 , i.e. to situations when a condition or part of a condition is always false / true. In the case of the XNU Kernel project, all these errors, in my opinion, are related to typos. PVS-Studio generally very well identifies typos.

    Fragment N1

    int
    key_parse(
          struct mbuf *m,
          struct socket *so)
    {
      ....
      if ((m->m_flags & M_PKTHDR) == 0 ||
          m->m_pkthdr.len != m->m_pkthdr.len) {
        ipseclog((LOG_DEBUG,
                  "key_parse: invalid message length.\n"));
        PFKEY_STAT_INCREMENT(pfkeystat.out_invlen);
        error = EINVAL;
        goto senderror;
      }
      ....
    }

    PVS-Studio warning: V501 CWE-570 There are identical sub-expressions 'm-> M_dat.MH.MH_pkthdr.len' to the left and to the right of the '! =' Operator. key.c 9442

    Because of a typo, a class member is compared to itself:

    m->m_pkthdr.len != m->m_pkthdr.len

    Part of the condition is always false and, as a result, the length of a message is checked incorrectly. It turns out that the program will continue to process incorrect data. Perhaps this is not scary, but many vulnerabilities are precisely due to the fact that certain input data turned out to be unverified or were not verified in sufficient way. So this place in the code definitely deserves the attention of developers.

    Fragment N2, N3

    #define VM_PURGABLE_STATE_MASK  3
    kern_return_t
    memory_entry_purgeable_control_internal(...., int *state)
    {
      ....
      if ((control == VM_PURGABLE_SET_STATE ||
           control == VM_PURGABLE_SET_STATE_FROM_KERNEL) &&
          (((*state & ~(VM_PURGABLE_ALL_MASKS)) != 0) ||
           ((*state & VM_PURGABLE_STATE_MASK) >
               VM_PURGABLE_STATE_MASK)))
        return(KERN_INVALID_ARGUMENT);
      ....
    }

    PVS-Studio warning: V560 CWE-570 A part of conditional expression is always false: ((* state & 3)> 3). vm_user.c 3415

    Let us consider in more detail this part of the expression:

    (*state & VM_PURGABLE_STATE_MASK) > VM_PURGABLE_STATE_MASK

    If you substitute the macro value, you get:

    (*state & 3) > 3

    The bitwise AND operation can only result in the values ​​0, 1, or 2. It makes no sense to check whether 0, 1, or 2 is greater than 3. It is very likely that the expression contains some typo.

    As in the previous case, a certain status is checked incorrectly, which may cause the processing of incorrect (false) data.

    Exactly the same error is found in the vm_map.c file. Apparently, part of the code was written using Copy-Paste. Warning: V560 CWE-570 A part of conditional expression is always false: ((* state & 3)> 3). vm_map.c 15809

    Fragment N4

    void
    pat_init(void)
    {
      boolean_t  istate;
      uint64_t  pat;
      if (!(cpuid_features() & CPUID_FEATURE_PAT))
        return;
      istate = ml_set_interrupts_enabled(FALSE);
      pat = rdmsr64(MSR_IA32_CR_PAT);
      DBG("CPU%d PAT: was 0x%016llx\n", get_cpu_number(), pat);
      /* Change PA6 attribute field to WC if required */
      if ((pat & ~(0x0FULL << 48)) != (0x01ULL << 48)) {
        mtrr_update_action(CACHE_CONTROL_PAT);
      }
      ml_set_interrupts_enabled(istate);
    }

    PVS-Studio Warning: V547 CWE-571 Expression is always true. mtrr.c 692

    Let's look at a meaningless check that a typo most likely crept into:

    (pat & ~(0x0FULL << 48)) != (0x01ULL << 48)

    We calculate some expressions:
    • ~ (0x0FULL << 48) = 0xFFF0FFFFFFFFFFFF
    • (0x01ULL << 48) = 0x0001000000000000

    The expression (pat & [0xFFF0FFFFFFFFFFFF]) cannot result in a value of 0x0001000000000000 . The condition is always true. As a result, the mtrr_update_action function is always called.

    Fragment N5

    Now, in my opinion, there will be a very beautiful typo: instead of == they wrote = .

    typedef enum {
      CMODE_WK = 0,
      CMODE_LZ4 = 1,
      CMODE_HYB = 2,
      VM_COMPRESSOR_DEFAULT_CODEC = 3,
      CMODE_INVALID = 4
    } vm_compressor_mode_t;
    void vm_compressor_algorithm_init(void) {
      ....
      assertf(((new_codec == VM_COMPRESSOR_DEFAULT_CODEC) ||
               (new_codec == CMODE_WK) ||
               (new_codec == CMODE_LZ4) || (new_codec = CMODE_HYB)),
              "Invalid VM compression codec: %u", new_codec);
      ....
    }

    PVS-Studio Warning: V768 CWE-571 The expression 'new_codec = CMODE_HYB' is of enum type. It is odd that it is used as an expression of a Boolean-type. vm_compressor_algorithms.c 419

    In the process of checking the condition, the variable new_codec is assigned the value 2. As a result, the condition is always true and the assert macro does not actually check anything.

    A mistake could be harmless. Well, think, an assert macro did not check something - it doesn’t matter. However, in addition to this, also the debug version does not work correctly. After all, the value of the new_codec variable spoils and the codec is not used as required.

    Fragment N6, N7

    void
    pbuf_copy_back(pbuf_t *pbuf, int off, int len, void *src)
    {
      VERIFY(off >= 0);
      VERIFY(len >= 0);
      VERIFY((u_int)(off + len) <= pbuf->pb_packet_len);
      if (pbuf->pb_type == PBUF_TYPE_MBUF)
        m_copyback(pbuf->pb_mbuf, off, len, src);
      else
      if (pbuf->pb_type == PBUF_TYPE_MBUF) {
        if (len)
          memcpy(&((uint8_t *)pbuf->pb_data)[off], src, len);
      } else
        panic("%s: bad pb_type: %d", __func__, pbuf->pb_type);
    }

    PVS-Studio warning: V517 CWE-570 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 340, 343. pf_pbuf.c 340

    For clarity, I will highlight the essence:

    if (A)
      foo();
    else
      if (A)
        Unreachable_code;
      else
        panic();

    If condition A is true, then the body of the first if statement is true . If this is not so, then re-checking does not make sense and the panic function is called immediately . Part of the code is generally unattainable.

    Here either some kind of error in the logic, or a typo in one of the conditions.

    Below in the same file is the pbuf_copy_data function , which, most likely, was written using Copy-Paste and contains the same error. Warning: V517 CWE-570 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 358, 361. pf_pbuf.c 358

    CWE-670: Always-Incorrect Control Flow Implementation


    The defect of CWE-670 suggests that in the code, most likely, something does not work the way the programmer intended.

    Fragment N8, N9, N10

    static void
    in_ifaddr_free(struct ifaddr *ifa)
    {
      IFA_LOCK_ASSERT_HELD(ifa);
      if (ifa->ifa_refcnt != 0) {
        panic("%s: ifa %p bad ref cnt", __func__, ifa);
        /* NOTREACHED */
      } if (!(ifa->ifa_debug & IFD_ALLOC)) {
        panic("%s: ifa %p cannot be freed", __func__, ifa);
        /* NOTREACHED */
      }
      if (ifa->ifa_debug & IFD_DEBUG) {
      ....
    }

    PVS-Studio Warning: V646 CWE-670 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. in.c 2010

    Perhaps there is no error in this code. However, this place looks very suspicious:

    } if (!(ifa->ifa_debug & IFD_ALLOC)) {

    The anomaly is that writing is not accepted. It would be more logical to start writing if from a new line. Code authors should check this place. Perhaps the else keyword is missing here and the code should be like this:

    } else if (!(ifa->ifa_debug & IFD_ALLOC)) {

    Or you just need to add line breaks so that this code is not confused by either the analyzer or the colleagues accompanying this code.

    Similar suspicious places can be found here:

    • V646 CWE-670 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. kern_malloc.c 836
    • V646 CWE-670 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. ipc_kmsg.c 4229

    Fragment N11, N12, N13, N14

    int
    dup2(proc_t p, struct dup2_args *uap, int32_t *retval)
    {
      ....
      while ((fdp->fd_ofileflags[new] & UF_RESERVED) == UF_RESERVED)
      {
        fp_drop(p, old, fp, 1);
        procfdtbl_waitfd(p, new);
    #if DIAGNOSTIC
        proc_fdlock_assert(p, LCK_MTX_ASSERT_OWNED);
    #endif
        goto startover;
      }  
      ....
    startover:
      ....
    }

    PVS-Studio Warning: V612 CWE-670 An unconditional 'goto' within a loop. kern_descrip.c 628

    This is very strange code. Note that the body of the while statement ends with the goto statement . In this case, the continue statement is not used anywhere in the body of the loop . This means that the body of the cycle will be executed no more than once.

    Why was it necessary to create a loop if it still does not perform more than one iteration? It would be better to use the if statement , then this would not raise questions. I think that this is a mistake, and something is written incorrectly in the loop. For example, perhaps a condition is missing before the goto statement .

    Similar “one-time” cycles occur 3 more times:
    • V612 CWE-670 An unconditional 'goto' within a loop. tty.c 1084
    • V612 CWE-670 An unconditional 'goto' within a loop. vm_purgeable.c 842
    • V612 CWE-670 An unconditional 'return' within a loop. kern_credential.c 930

    Dereferencing Null Pointers: CWE-476, CWE-628, CWE-690


    There are various reasons due to which dereferencing of a null pointer can occur and the PVS-Studio analyzer, depending on the situation, can assign them different CWE-IDs:
    • CWE-476 : NULL Pointer Dereference
    • CWE-628 : Function Call with Incorrectly Specified Arguments
    • CWE-690 : Unchecked Return Value to NULL Pointer Dereference

    When writing an article, I considered it reasonable to collect all errors of this type in one section.

    Fragment N15

    I'll start with complex and large functions. First, consider the function netagent_send_error_response , in which the pointer passed in the session argument is dereferenced .

    static int
    netagent_send_error_response(
      struct netagent_session *session, u_int8_t message_type,
                   u_int32_t message_id, u_int32_t error_code)
    {
      int error = 0;
      u_int8_t *response = NULL;
      size_t response_size = sizeof(struct netagent_message_header);
      MALLOC(response, u_int8_t *, response_size,
             M_NETAGENT, M_WAITOK);
      if (response == NULL) {
        return (ENOMEM);
      }
      (void)netagent_buffer_write_message_header(.....);
      if ((error = netagent_send_ctl_data(session->control_unit,
          (u_int8_t *)response, response_size))) {
        NETAGENTLOG0(LOG_ERR, "Failed to send response");
      }
      FREE(response, M_NETAGENT);
      return (error);
    }

    Note that the session pointer is dereferenced in the session-> control_unit expression without any prior validation. Whether the null pointer is dereferenced or not depends on what actual arguments are passed to this function.

    Now let's see how the netagent_send_error_response function discussed above is used in the netagent_handle_unregister_message function :

    static void
    netagent_handle_unregister_message(
      struct netagent_session *session, ....)
    #pragma unused(payload_length, packet, offset)
      u_int32_t response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
      if (session == NULL) {
        NETAGENTLOG0(LOG_ERR, "Failed to find session");
        response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
        goto fail;
      }
      netagent_unregister_session_wrapper(session);
      netagent_send_success_response(session, .....);
      return;
    fail:
      netagent_send_error_response(
        session, NETAGENT_MESSAGE_TYPE_UNREGISTER, message_id,
        response_error);
    }

    PVS-Studio Warning: V522 CWE-628 Dereferencing of the null pointer 'session' might take place. The null pointer is passed into 'netagent_send_error_response' function. Inspect the first argument. Check lines: 427, 972. network_agent.c 427

    Data Flow analysis implemented in PVS-Studio manifests itself here. The analyzer observes that if the session pointer was NULL , then some information is written to the log, after which the transition to the fail label occurs .

    The following is a call to the netagent_send_error_response function :

    fail:
      netagent_send_error_response(
        session, NETAGENT_MESSAGE_TYPE_UNREGISTER, message_id,
        response_error);

    Please note that the unfortunate session pointer , which is NULL, is passed to the function as the actual argument .

    As we know, in the netagent_send_error_response function there is no protection in this case, and a null pointer will be dereferenced.

    Fragment N16

    The following situation is similar to the previous one. The function code will be shorter, but you will have to deal with it just as slowly and in detail.

    void *
    pf_lazy_makewritable(struct pf_pdesc *pd, pbuf_t *pbuf, int len)
    {
      void *p;
      if (pd->lmw < 0)
        return (NULL);
      VERIFY(pbuf == pd->mp);
      p = pbuf->pb_data;
      if (len > pd->lmw) {
      ....
    }

    Note that the pbuf pointer is dereferenced without first checking for NULL . The code has a check “VERIFY (pbuf == pd-> mp)”. However, pd-> mp may turn out to be NULL , so verification cannot be considered protection against NULL .

    Note. Please remember that I am completely unfamiliar with the XNU Kernel code and may be wrong. Perhaps pd-> mp will never store a NULL value . Then all my reasoning is wrong and there is no mistake here. Nevertheless, such a code is still better to check once again. Let's

    continue and see how the considered pf_lazy_makewritable function is used .

    static int
    pf_test_state_icmp(....)
    {
      ....
      if (pf_lazy_makewritable(pd, NULL,
          off + sizeof (struct icmp6_hdr)) ==
          NULL)
        return (PF_DROP);
      ....
    }

    PVS-Studio Warning: V522 CWE-628 Dereferencing of the null pointer 'pbuf' might take place. The null pointer is passed into 'pf_lazy_makewritable' function. Inspect the second argument. Check lines: 349, 7460. pf.c 349 NULL is passed

    as the second actual argument to the pf_lazy_makewritable function . It is very strange. Suppose a programmer thinks that the program will protect "VERIFY (pbuf == pd-> mp)" from the null pointer. Then the question arises: why write such code at all? Why call a function by passing an obviously invalid argument? Therefore, it seems to me that the pf_lazy_makewritable function is actually



    should be able to accept a null pointer and somehow handle this case in a special way, but does not. This code deserves the most thorough verification by the programmer, and the PVS-Studio analyzer is definitely right, drawing our attention to it.

    Fragment N17

    You can relax a bit: consider a simple case.

    typedef struct vnode * vnode_t;
    int 
    cache_lookup_path(...., vnode_t dp, ....)
    {
      ....
      if (dp && (dp->v_flag & VISHARDLINK)) {
        break;
      }
      if ((dp->v_flag & VROOT)  ||
          dp == ndp->ni_rootdir ||
          dp->v_parent == NULLVP)
        break;
      ....
    }

    PVS-Studio Warning: V522 CWE-690 There might be dereferencing of a potential null pointer 'dp'. vfs_cache.c 1449 Take a

    look at the check:

    if (dp && (dp->v_flag & VISHARDLINK))

    It tells us that the dp pointer may be null. However, then the pointer is dereferenced without a preliminary check:

    if ((dp->v_flag & VROOT) || ....)

    Fragment N18

    In the previous example, we met a situation where the pointer is checked before dereferencing, and then the code was forgotten in the code. But much more often you can meet a situation where at first the pointer is dereferenced, and only then checked. The XNU Kernel project code was no exception. To better understand what we are talking about, we will first consider a synthetic example:

    p[n] = 1;
    if (!p) return false;

    Let’s now take a look at how such errors look like in reality. Let's start with the name comparison function. The comparison functions are very insidious :).

    bool
    IORegistryEntry::compareName(....) const
    {
      const OSSymbol *  sym = copyName();
      bool    isEqual;
      isEqual = sym->isEqualTo( name );   // <=
      if( isEqual && matched) {
        name->retain();
        *matched = name;
      }
      if( sym)                            // <=
        sym->release();
      return( isEqual );
    }

    PVS-Studio Warnings: V595 CWE-476 The 'sym' pointer was utilized before it was verified against nullptr. Check lines: 889, 896. IORegistryEntry.cpp 889

    I highlighted the lines of code that we were interested in with comments of the form "// <=". As you can see, at first the pointer is dereferenced. Further in the code there is a check for equality of the nullptr pointer . But it’s immediately clear that if the pointer is null, then the null pointer will be dereferenced and the function, in fact, is not ready for such a situation.

    Fragment N19

    The following error occurred due to a typo.

    static int
    memorystatus_get_priority_list(
      memorystatus_priority_entry_t **list_ptr, size_t *buffer_size,
      size_t *list_size, boolean_t size_only) 
    {
      ....
      *list_ptr = (memorystatus_priority_entry_t*)kalloc(*list_size);
      if (!list_ptr) {
        return ENOMEM;
      }
      ....
    }

    PVS-Studio Warning: V595 CWE-476 The 'list_ptr' pointer was utilized before it was verified against nullptr. Check lines: 7175, 7176. kern_memorystatus.c 7175

    The analyzer sees that the variable is first dereferenced, and in the next line it is checked for nullptr equality . This interesting error arose because the programmer forgot to write the character '*'. In fact, the code should have been like this:

    *list_ptr = (memorystatus_priority_entry_t*)kalloc(*list_size);
    if (!*list_ptr) {
      return ENOMEM;
    }

    We can say that the error was detected indirectly. However, this does not matter, because the most important thing is that the analyzer paid attention to the abnormal code and we saw an error.

    Fragment N20 - N35

    In the XNU Kernel code, a lot of errors are detected due to the V595 diagnostics. However, to consider all of them will be boring. Therefore, I will analyze only one more case, and then I will give a list of messages indicating errors.

    inline void
    inp_decr_sndbytes_unsent(struct socket *so, int32_t len)
    {
      struct inpcb *inp = (struct inpcb *)so->so_pcb;
      struct ifnet *ifp = inp->inp_last_outifp;
      if (so == NULL || !(so->so_snd.sb_flags & SB_SNDBYTE_CNT))
        return;
      if (ifp != NULL) {
        if (ifp->if_sndbyte_unsent >= len)
          OSAddAtomic64(-len, &ifp->if_sndbyte_unsent);
        else
          ifp->if_sndbyte_unsent = 0;
      }
    }

    PVS-Studio Warning: V595 CWE-476 The 'so' pointer was utilized before it was verified against nullptr. Check lines: 3450, 3453. in_pcb.c 3450 I

    suggest the reader to independently track the fate of the so pointer and make sure that the code is written incorrectly.

    Other errors:

    • V595 CWE-476 The 'startDict' pointer was utilized before it was verified against nullptr. Check lines: 3369, 3373. IOService.cpp 3369
    • V595 CWE-476 The 'job' pointer was utilized before it was verified against nullptr. Check lines: 4083, 4085. IOService.cpp 4083
    • V595 CWE-476 The 'typeinst' pointer was utilized before it was verified against nullptr. Check lines: 176, 177. OSMetaClass.cpp 176
    • V595 CWE-476 The 'name' pointer was utilized before it was verified against nullptr. Check lines: 385, 392. devfs_tree.c 385
    • V595 CWE-476 The 'collection' pointer was utilized before it was verified against nullptr. Check lines: 71, 75. OSCollectionIterator.cpp 71
    • V595 CWE-476 The 'ifp' pointer was utilized before it was verified against nullptr. Check lines: 2014, 2018. dlil.c 2014
    • V595 CWE-476 The 'fakeif' pointer was utilized before it was verified against nullptr. Check lines: 561, 566. if_fake.c 561
    • V595 CWE-476 The 'sb' pointer was utilized before it was verified against nullptr. Check lines: 138, 140. in_pcblist.c 138
    • V595 CWE-476 The 'tp' pointer was utilized before it was verified against nullptr. Check lines: 2603, 2610. tcp_subr.c 2603
    • V595 CWE-476 The 'str_id' pointer was utilized before it was verified against nullptr. Check lines: 1812, 1817. kdebug.c 1812
    • V595 CWE-476 The 'sessp' pointer was utilized before it was verified against nullptr. Check lines: 191, 194. subr_prf.c 191
    • V595 CWE-476 The 'sessp' pointer was utilized before it was verified against nullptr. Check lines: 1463, 1469. tty.c 1463
    • V595 CWE-476 The 'so' pointer was utilized before it was verified against nullptr. Check lines: 6714, 6719. uipc_socket.c 6714
    • V595 CWE-476 The 'uap' pointer was utilized before it was verified against nullptr. Check lines: 314, 320. nfs_upcall.c 314
    • V595 CWE-476 The 'xfromname' pointer was utilized before it was verified against nullptr. Check lines: 3986, 4006. kpi_vfs.c 3986
    • Note. In fact, I did not carefully watch all the warnings of this type. Therefore, in fact, there may be more errors.

    Fragment N36, N37

    And the last pair of errors on the use of null pointers.

    static void
    feth_start(ifnet_t ifp)
    {
      ....
      if_fake_ref  fakeif;
      ....
      if (fakeif != NULL) {
        peer = fakeif->iff_peer;
        flags = fakeif->iff_flags;
      }
      /* check for pending TX */
      m = fakeif->iff_pending_tx_packet;
      ....
    }

    PVS-Studio Warning: V1004 CWE-476 The 'fakeif' pointer was used unsafely after it was verified against nullptr. Check lines: 566, 572. if_fake.c 572

    I think commenting on this code is not required. Just see how the fakeif pointer is checked and used .

    Last similar case: V1004 CWE-476 The 'rt-> rt_ifp' pointer was used unsafely after it was verified against nullptr. Check lines: 138, 140. netsrc.c 140

    CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer


    I met a couple of errors related to going out of the buffer border. A very unpleasant kind of mistake for such a responsible project as XNU Kernel.

    Different options for going abroad the array can be classified by different CWE IDs, but in this case, the analyzer chose CWE-119 .

    Fragment N38

    First, let's look at how some macros are declared.

    #define  IFNAMSIZ   16
    #define  IFXNAMSIZ  (IFNAMSIZ + 8)
    #define MAX_ROUTE_RULE_INTERFACES 10

    It is important for us to remember that:
    • IFXNAMSIZ = 24
    • MAX_ROUTE_RULE_INTERFACES = 10

    Now consider a function where it is possible to go beyond the buffer border when using the snprintf and memset functions . Those. 2 errors occur.

    static inline const char *
    necp_get_result_description(....)
    {
      ....
      char interface_names[IFXNAMSIZ][MAX_ROUTE_RULE_INTERFACES];
      ....
      for (index = 0; index < MAX_ROUTE_RULE_INTERFACES; index++) {
        if (route_rule->exception_if_indices[index] != 0) {
          ifnet_t interface = ifindex2ifnet[....];
          snprintf(interface_names[index],
                   IFXNAMSIZ, "%s%d", ifnet_name(interface),
                   ifnet_unit(interface));
        } else {
          memset(interface_names[index], 0, IFXNAMSIZ);
        }
      }
      ....
    }

    PVS-Studio warnings:
    • V512 CWE-119 A call of the '__builtin___memcpy_chk' function will lead to a buffer overflow. - ADDITIONAL IN CURRENT necp_client.c 1459
    • V557 CWE-787 Array overrun is possible. The value of 'length - 1' index could reach 23. - ADDITIONAL IN CURRENT necp_client.c 1460

    Notice how the two-dimensional interface_names array is declared :

    char interface_names[IFXNAMSIZ][MAX_ROUTE_RULE_INTERFACES];
    // то есть: char interface_names[24][10];

    But this uses this array as if it were like this:

    char interface_names[MAX_ROUTE_RULE_INTERFACES][IFXNAMSIZ];
    // то есть: char interface_names[10][24];

    It turns out some kind of mess from the data.

    Without thinking, someone might say that there is nothing to worry about, because both arrays occupy the same number of bytes.

    No, everything is bad. The elements of the interface_names [10..23] [....] array are not used, since the index variable in the loop takes the values ​​[0..9]. But the elements of interface_names [0..9] [....] begin to "fit" on each other. Those. some data overwrites others.

    The result is a complete bullshit. Part of the array remains uninitialized, and the other part contains a mess when data was written over already recorded data.

    Fragment N39

    Below in the same file necp_client.c there is another function containing very similar errors.

    #define  IFNAMSIZ   16
    #define  IFXNAMSIZ  (IFNAMSIZ + 8)
    #define NECP_MAX_PARSED_PARAMETERS 16
    struct necp_client_parsed_parameters {
      ....
      char prohibited_interfaces[IFXNAMSIZ]
                                      [NECP_MAX_PARSED_PARAMETERS];
      ....
    };
    static int
    necp_client_parse_parameters(....,
      struct necp_client_parsed_parameters *parsed_parameters)
    {
      ....
      u_int32_t length = ....;
      ....
      if (length <= IFXNAMSIZ && length > 0) {
        memcpy(parsed_parameters->prohibited_interfaces[
                                         num_prohibited_interfaces],
               value, length);
        parsed_parameters->prohibited_interfaces[
                        num_prohibited_interfaces][length - 1] = 0;
      ....
    }

    PVS-Studio warning:
    • V512 CWE-119 A call of the '__builtin___memcpy_chk' function will lead to a buffer overflow. - ADDITIONAL IN CURRENT necp_client.c 1459
    • V557 CWE-787 Array overrun is possible. The value of 'length - 1' index could reach 23. - ADDITIONAL IN CURRENT necp_client.c 1460

    All the same. Array:

    char prohibited_interfaces[IFXNAMSIZ][NECP_MAX_PARSED_PARAMETERS];

    handled as if it were:

    char prohibited_interfaces[NECP_MAX_PARSED_PARAMETERS][IFXNAMSIZ];

    CWE-563: Assignment to Variable without Use


    CWE-563 defects detected by PVS-Studio are often the result of typos. Now just such a beautiful typo will be considered.

    Fragment N40

    uint32_t
    gss_krb5_3des_unwrap_mbuf(....)
    {
      ....
      for (cflag = 1; cflag >= 0; cflag--) {
        *minor = gss_krb5_3des_token_get(
           ctx, &itoken, wrap, &hash, &offset, &length, reverse);
        if (*minor == 0)
          break;
        wrap.Seal_Alg[0] = 0xff;
        wrap.Seal_Alg[0] = 0xff;
      }
      ....
    }

    PVS-Studio Warning: V519 CWE-563 The 'wrap.Seal_Alg [0]' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 2070, 2071. gss_krb5_mech.c 2071

    Two times write the value 0xff to the same element of the array. I looked at the code next door and came to the conclusion that I really wanted to write here:

    wrap.Seal_Alg[0] = 0xff;
    wrap.Seal_Alg[1] = 0xff;

    Judging by the name of the function, it is associated with a network authentication protocol. And such a blunder ... What a horror.

    You can buy PVS-Studio here . Our analyzer will help prevent many such errors!

    Fragment N41, N42, N43, N44

    static struct mbuf *
    pf_reassemble(struct mbuf *m0, struct pf_fragment **frag,
        struct pf_frent *frent, int mff)
    {
      ....
      m->m_pkthdr.csum_flags &= ~CSUM_PARTIAL;
      m->m_pkthdr.csum_flags =
          CSUM_DATA_VALID | CSUM_PSEUDO_HDR |
          CSUM_IP_CHECKED | CSUM_IP_VALID;
      ....
    }

    PVS-Studio warning: V519 CWE-563 The 'm-> M_dat.MH.MH_pkthdr.csum_flags' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 758, 759. pf_norm.c 759

    Line:

    m->m_pkthdr.csum_flags &= ~CSUM_PARTIAL;

    makes no practical sense. In the next line, the variable m-> m_pkthdr.csum_flags will be assigned a new value. I don’t know what the correct code should actually look like, but I dare to assume that they forgot the '|' character. In my humble opinion, the code should look like this:

    m->m_pkthdr.csum_flags &= ~CSUM_PARTIAL;
    m->m_pkthdr.csum_flags |=
        CSUM_DATA_VALID | CSUM_PSEUDO_HDR |
        CSUM_IP_CHECKED | CSUM_IP_VALID;

    There are 3 more warnings that indicate similar errors:
    • V519 CWE-563 The 'm-> M_dat.MH.MH_pkthdr.csum_flags' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 1349, 1350. pf_norm.c 1350
    • V519 CWE-563 The 'm-> M_dat.MH.MH_pkthdr.csum_flags' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 2984, 2985. ip_input.c 2985
    • V519 CWE-563 The 'm-> M_dat.MH.MH_pkthdr.csum_flags' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 773, 774. frag6.c 774


    CWE-14: Compiler Removal of Code to Clear Buffers


    A very insidious kind of defect that is invisible in the debug version. If the reader is not already familiar with it, then before continuing reading, I suggest that you familiarize yourself with the following links:
    1. Safe Clearing of Private Data .
    2. V597 . The compiler could delete the 'memset' function call, which is used to flush 'Foo' buffer. The RtlSecureZeroMemory () function should be used to erase the private data.
    3. CWE-14 : Compiler Removal of Code to Clear Buffers.

    If the reader does not understand why they should overwrite private data stored in memory, then I recommend the article " Overwrite memory - why? ".

    So, overwriting private data in memory is important, but sometimes the compiler removes the corresponding code, since, from its point of view, it is superfluous. Let's see what's interesting about XNU Kernel.

    Fragment N45

    __private_extern__ void
    YSHA1Final(unsigned char digest[20], YSHA1_CTX* context)
    {
      u_int32_t i, j;
      unsigned char finalcount[8];
      ....
      /* Wipe variables */
      i = j = 0;
      memset(context->buffer, 0, 64);
      memset(context->state, 0, 20);
      memset(context->count, 0, 8);
      memset(finalcount, 0, 8);           // <=
    #ifdef SHA1HANDSOFF
      YSHA1Transform(context->state, context->buffer);
    #endif
    }

    PVS-Studio Warning: V597 CWE-14 The compiler could delete the 'memset' function call, which is used to flush 'finalcount' buffer. The memset_s () function should be used to erase the private data. sha1mod.c 188 In

    order to optimize the Release version, the compiler has the right to delete the line of code that I noted with the comment "// <=". Almost certainly he would.

    Fragment N46

    __private_extern__ void
    YSHA1Transform(u_int32_t state[5], const unsigned char buffer[64])
    {
      u_int32_t a, b, c, d, e;
      ....
      state[0] += a;
      state[1] += b;
      state[2] += c;
      state[3] += d;
      state[4] += e;
      /* Wipe variables */
      a = b = c = d = e = 0;
    }

    PVS-Studio Warning: V1001 CWE-563 The 'a' variable is assigned but is not used until the end of the function. sha1mod.c 120

    The compiler has the right not to generate code that resets the variables, since they are no longer used in the function.

    I want to note that the PVS-Studio analyzer interpreted this suspicious situation as CWE-563 . The fact is that the same defect can often be interpreted as different CWEs, and in this case, the analyzer chose CWE-563. However, I decided to attribute this code specifically to CWE-14, as this more accurately explains what is wrong with this code.

    CWE-783: Operator Precedence Logic Error


    A defect in CWE-783 occurs where the programmer mixed up the operation priorities and wrote code that does not work as intended. Often such errors are made due to carelessness or missing parentheses.

    Fragment N47

    int
    getxattr(....)
    {
      ....
      if ((error = copyinstr(uap->attrname, attrname,
                             sizeof(attrname), &namelen) != 0)) {
        goto out;
      }
      ....
    out:
      ....
      return (error);
    }

    PVS-Studio Warning: V593 CWE-783 Consider reviewing the expression of the 'A = B! = C' kind. The expression is calculated as following: 'A = (B! = C)'. vfs_syscalls.c 10574

    Classical error. I meet a large number of such errors in various programs ( proof ). The root cause is that some programmers for some reason tend to cram the most in one line.

    As a result, instead of:

    Status s = foo();
    if (s == Error)
      return s;

    they write:

    Status s;
    if (s = foo() == Error)
      return s;

    And they introduce an error into the code.
    • The programmer expects the expression to evaluate as follows: (s = foo ()) == Error.
    • In fact, the expression evaluates to: s = (foo () == Error).

    As a result, the return statement returns an invalid error status of 1, and not a value equal to the Error constant .

    I regularly criticize such code and recommend that you do not “push” several actions into one line at once. "Push" does not greatly reduce the size of the code, but it provokes various errors. I propose to read more about this in my mini-book " The main issue of programming, refactoring and all that ." See chapters:
    • 11. Do not be greedy on the lines of code
    • 16. "See how I can" - unacceptable in programming

    Let's get back to the code from XNU Kernel. In case of an error, the getxattr function will return the value 1, not the real error code.

    Fragment N48 - N52

    static void
    memorystatus_init_snapshot_vmstats(
      memorystatus_jetsam_snapshot_t *snapshot)
    {
      kern_return_t kr = KERN_SUCCESS;
      mach_msg_type_number_t  count = HOST_VM_INFO64_COUNT;
      vm_statistics64_data_t  vm_stat;
      if ((kr = host_statistics64(.....) != KERN_SUCCESS)) {
        printf("memorystatus_init_jetsam_snapshot_stats: "
               "host_statistics64 failed with %d\n", kr);
        memset(&snapshot->stats, 0, sizeof(snapshot->stats));
      } else {
    +  ....
    }

    PVS-Studio Warning: V593 CWE-783 Consider reviewing the expression of the 'A = B! = C' kind. The expression is calculated as following: 'A = (B! = C)'. kern_memorystatus.c 4554 Only two values ​​can be assigned to the kr

    variable : 0 or 1. Because of this, the printf function always prints the number 1, rather than the actual status returned by the host_statistics64 function . The article is big. I think I’ll bore her not only myself, but also the readers. Therefore, I reduce the number of fragments considered in the article. Other similar defects are uninteresting to consider, and I will limit myself to a list of messages:





    • V593 CWE-783 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. vfs_syscalls.c 10654
    • V593 CWE-783 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. vfs_syscalls.c 10700
    • V593 CWE-783 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. vfs_syscalls.c 10759
    • V593 CWE-783 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. kern_exec.c 2297

    CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior


    There are a lot of ways to get undefined or unspecified behavior in a C or C ++ program. Therefore, the PVS-Studio analyzer implements a lot of diagnostics aimed at identifying such problems: V567, V610, V611, V681, V704, V708, V726, V736.

    In the case of XNU, the analyzer detected only two weaknesses of the CWE-758 , associated with undefined behavior caused by the shift of negative numbers.

    Fragment N53, N54

    static void
    pfr_prepare_network(union sockaddr_union *sa, int af, int net)
    {
      ....
      sa->sin.sin_addr.s_addr = net ? htonl(-1 << (32-net)) : 0;
      ....
    }

    PVS-Studio Warning: V610 CWE-758 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. pf_table.c 976

    Shifting a negative number to the left leads to undefined behavior. In practice, this code may well work exactly as the programmer expects. But still, this code is incorrect and should be fixed. This can be done like this:

    htonl((unsigned)(-1) << (32-net))

    The PVS-Studio analyzer finds another shift here: V610 CWE-758 Undefined behavior. Check the shift operator '<<'. The left operand '-1' is negative. pf_table.c 983

    CWE-401: Improper Release of Memory Before Removing Last Reference ('Memory Leak')


    We should praise the XNU Kernel developers for the fact that the analyzer could not find problems with memory leaks ( CWE-401 ). There are only 3 suspicious places when the delete statement is not called when an object is initialized . However, I'm not sure that this is exactly the mistake.

    Fragment N55, N56, N57

    IOService * IODTPlatformExpert::createNub(IORegistryEntry * from)
    {
      IOService *    nub;
      nub = new IOPlatformDevice;
      if (nub) {
        if( !nub->init( from, gIODTPlane )) {
          nub->free();
          nub = 0;
        }
      }
      return (nub);
    }

    V773 CWE-401 The 'nub' pointer was assigned values ​​twice without releasing the memory. A memory leak is possible. IOPlatformExpert.cpp 1287

    If the init function cannot initialize the object, then a memory leak may occur. In my opinion, the delete operator is missing here , and it should have been written like this:

    if( !nub->init( from, gIODTPlane )) {
      nub->free();
      delete nub;
      nub = 0;
    }

    I'm not sure I'm right. Perhaps the free function itself destroys the object by performing the operation “delete * this;”. I did not carefully understand, because by the time I got to these warnings I was already tired.

    Similar analyzer warnings:
    • V773 CWE-401 The 'inst' pointer was assigned values ​​twice without releasing the memory. A memory leak is possible. IOUserClient.cpp 246
    • V773 CWE-401 The 'myself' pointer was assigned values ​​twice without releasing the memory. A memory leak is possible. IOPMrootDomain.cpp 9151


    CWE-129: Improper Validation of Array Index


    The CWE-129 defect indicates that the variables used to index elements in the array are incorrectly or insufficiently checked. As a result, an outbound array may occur.

    Fragment N58 - N61

    IOReturn
    IOStateReporter::updateChannelValues(int channel_index)
    {
      ....
      state_index = _currentStates[channel_index];
      if (channel_index < 0 ||
          channel_index > (_nElements - state_index)
                            / _channelDimension) {
        result = kIOReturnOverrun; goto finish;
      }
      ....
    }

    PVS-Studio Warning: V781 CWE-129 The value of the 'channel_index' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 852, 855. IOStateReporter.cpp 852

    Protection against negative values ​​is incorrectly implemented. At first, the element is always retrieved from the array, and only then is the verification that the index is not negative.

    I think this code should be rewritten as follows:

    IOReturn
    IOStateReporter::updateChannelValues(int channel_index)
    {
      ....
      if (channel_index < 0)
      {
        result = kIOReturnOverrun; goto finish;
      }
      state_index = _currentStates[channel_index];
      if (channel_index > (_nElements - state_index)
                            / _channelDimension) {
        result = kIOReturnOverrun; goto finish;
      }
      ....
    }

    Perhaps you should also add checks that the value of channel_index does not exceed the size of the array. I am not familiar with the code, so I leave this to the discretion of the XNU Kernel developers.

    Similar errors:

    • V781 CWE-129 The value of the 'channel_index' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 651, 654. IOStateReporter.cpp 651
    • V781 CWE-129 The value of the 'pri' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 267, 269. pktsched_fq_codel.c 267
    • V781 CWE-129 The value of the 'pcid' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 224, 225. pmap_pcid.c 224

    CWE-480: Use of Incorrect Operator


    Defects in the CWE-480 are usually associated with some kind of typo in the expressions. They, as a rule, are not very many, but they are very funny. You look at such errors and wonder how you could make them. Nevertheless, as we have repeatedly demonstrated in articles, even highly qualified programmers are not immune from such errors.

    Fragment N62

    #define NFS_UC_QUEUE_SLEEPING  0x0001
    static void
    nfsrv_uc_proxy(socket_t so, void *arg, int waitflag)
    {
      ....
      if (myqueue->ucq_flags | NFS_UC_QUEUE_SLEEPING)
        wakeup(myqueue);
      ....
    }

    PVS-Studio Warning: V617 CWE-480 Consider inspecting the condition. The '0x0001' argument of the '|' bitwise operation contains a non-zero value. nfs_upcall.c 331

    Some entity "wakes up" more often than necessary. Rather, it is “woken up” constantly, regardless of the condition. Most likely, it should be written here:

    if (myqueue->ucq_flags & NFS_UC_QUEUE_SLEEPING)
      wakeup(myqueue);


    CWE-665: Improper Initialization


    PVS-Studio analyzer could not classify the following error according to CWE. In my opinion, we are dealing with the CWE-665 .

    Fragment N63

    extern void bzero(void *, size_t);
    static struct thread  thread_template, init_thread;
    struct thread {
      ....
      struct thread_qos_override {
        struct thread_qos_override  *override_next;
        uint32_t  override_contended_resource_count;
        int16_t    override_qos;
        int16_t    override_resource_type;
        user_addr_t  override_resource;
      } *overrides;
      ....
    };
    void
    thread_bootstrap(void)
    {
      ....
      bzero(&thread_template.overrides,
            sizeof(thread_template.overrides));
      ....
    }

    PVS-Studio warning: V568 It's odd that 'sizeof ()' operator evaluates the size of a pointer to a class, but not the size of the 'thread_template.overrides' class object. thread.c 377

    We took the address of the variable that stores the pointer, and reset this variable using the bzero function . In fact, they just wrote nullptr to the pointer.

    Using the bzero function is a very strange unnatural way to nullify a variable. It was much easier to write:

    thread_template.overrides = NULL;

    From this I conclude that they wanted to reset the buffer, but accidentally reset the pointer. That is, the correct code should have been like this:

    bzero(thread_template.overrides,
          sizeof(*thread_template.overrides));

    CWE-691: Insufficient Control Flow Management


    CWE-691 indicates an abnormality in the sequence of instructions. Another anomaly is also possible - the design of the code does not match the way it works. This is exactly the case I came across in the XNU Kernel code.

    Fragment N64

    Hooray, we got to consider the last piece of code! Perhaps there are other errors that I did not notice while looking at the report issued by the analyzer, but I remind you that I did not have the goal of identifying as many errors as possible. In any case, the developers of XNU Kernel will be able to better than me to study the report, since they are familiar with the project code. So let's dwell on the beautiful number 64, which is consonant with the name of our site viva64 .

    Note. For those who are interested in where “viva64” came from, I suggest that you read the article “ How the PVS-Studio Project Started 10 Years Ago ”.

    void vm_page_release_startup(vm_page_t mem);
    void
    pmap_startup(
      vm_offset_t *startp,
      vm_offset_t *endp)
    {
      ....
      // -debug code remove
      if (2 == vm_himemory_mode) {
        for (i = 1; i <= pages_initialized; i++) {
          ....
        }
      }
      else
      // debug code remove-
      /*
       * Release pages in reverse order so that physical pages
       * initially get allocated in ascending addresses. This keeps
       * the devices (which must address physical memory) happy if
       * they require several consecutive pages.
       */
      for (i = pages_initialized; i > 0; i--) {
        if(fill) fillPage(....);
        vm_page_release_startup(&vm_pages[i - 1]);
      }
      ....
    }

    PVS-Studio Warning: V705 CWE-691 It is possible that 'else' block was forgotten or commented out, thus altering the program's operation logics. vm_resident.c 1248

    Perhaps there is no error here. However, the else keyword really confuses me . The code is designed in such a way that it seems as if the loop is always running. In fact, the loop is only executed when the condition (2 == vm_himemory_mode) is false.

    Conclusion


    In the macOS world, PVS-Studio , a new powerful static code analyzer , is able to detect errors and potential vulnerabilities in C and C ++ programs. I invite everyone to try our analyzer on their projects and evaluate its capabilities.

    Thank you for your attention and do not forget to share information with your colleagues that PVS-Studio is now available for macOS.



    If you want to share this article with an English-speaking audience, then please use the link to the translation: Andrey Karpov. PVS-Studio is now available on macOS: 64 weaknesses in the XNU Kernel .

    Have you read the article and have a question?
    Often our articles are asked the same questions. We collected the answers here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please see the list.

    Also popular now: