CVE-2015-9231 (Objective-C, published 2017-09-20T20:29Z)
Description: value: iTerm2 3.x before 3.1.1 allows remote attackers to discover passwords by reading DNS queries. A new (default) feature was added to iTerm2 version 3.0.0 (and unreleased 2.9.x versions such as 2.9.20150717) that resulted in a potential information disclosure. In an attempt to see whether the text under the cursor (or selected text) was a URL, the text would be sent as an unencrypted DNS query. This has the potential to result in passwords and other sensitive information being sent in cleartext without the user being aware.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-200 \u2014 Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Repository: https://github.com/gnachman/iTerm2
Files changed:
iTerm2.xcodeproj/project.pbxproj
sources/PTYTextView.m
sources/iTermAdvancedSettingsModel.h
sources/iTermAdvancedSettingsModel.m
sources/iTermTextDrawingHelper.h
sources/iTermTextDrawingHelper.m
Commit message: Fist swipe at removing DNS code


Vulnerable code:
		1D6ED85719AEA20D005A7799 /* AsyncHostLookupController.h in Headers */ = {isa = PBXBuildFile; fileRef = A6CFDAD5185D53C2005DC94B /* AsyncHostLookupController.h */; };
		A6C763051B45C52B00E3C992 /* AsyncHostLookupController.m in Sources */ = {isa = PBXBuildFile; fileRef = A6CFDAD6185D53C2005DC94B /* AsyncHostLookupController.m */; };
		A6CFDAD7185D53C2005DC94B /* AsyncHostLookupController.h in Headers */ = {isa = PBXBuildFile; fileRef = A6CFDAD5185D53C2005DC94B /* AsyncHostLookupController.h */; };
		A6CFDAD5185D53C2005DC94B /* AsyncHostLookupController.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = AsyncHostLookupController.h; sourceTree = "<group>"; tabWidth = 4; };
		A6CFDAD6185D53C2005DC94B /* AsyncHostLookupController.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncHostLookupController.m; sourceTree = "<group>"; tabWidth = 4; };
				A6CFDAD5185D53C2005DC94B /* AsyncHostLookupController.h */,
				A6CFDAD6185D53C2005DC94B /* AsyncHostLookupController.m */,
				1D6ED85719AEA20D005A7799 /* AsyncHostLookupController.h in Headers */,
				A6CFDAD7185D53C2005DC94B /* AsyncHostLookupController.h in Headers */,
				A6C763051B45C52B00E3C992 /* AsyncHostLookupController.m in Sources */,
//
//  AsyncHostLookupController.h
//  iTerm
//
//  Created by George Nachman on 12/14/13.
//
//

#import <Foundation/Foundation.h>

// Verify whether host names are valid. Runs in a separate thread and provides asynchronous results.
// Caches very aggressively.
@interface AsyncHostLookupController : NSObject

+ (instancetype)sharedInstance;

// Calls back to completion indicating whether |host| is an extant hostname. The BOOL is YES if the
// hostname resolves. |host| is passed as the second argument to completion. Does not block.
- (void)getAddressForHost:(NSString *)host
               completion:(void (^)(BOOL, NSString*))completion;

// Cancels the lookup for |hostname|.
- (void)cancelRequestForHostname:(NSString *)hostname;

@end
//
//  AsyncHostLookupController.m
//  iTerm
//
//  Created by George Nachman on 12/14/13.
//
//

#import "AsyncHostLookupController.h"
#import "DebugLogging.h"
#import "iTermAdvancedSettingsModel.h"
#include <netdb.h>

@implementation AsyncHostLookupController {
    // Created at initialization and used to perform blocking gethostbyname calls.
    dispatch_queue_t _queue;

    // Set of hostnames waiting to be looked up. If a hostname is removed from
    // this set then it won't be looked up when its turn comes around.
    NSMutableSet *_pending;

    // Maps hostname -> @YES or @NO, indicating if it resolved.
    NSMutableDictionary *_cache;
}

+ (instancetype)sharedInstance {
    static AsyncHostLookupController *instance;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _queue = dispatch_queue_create("AsyncHostLookupControllerQueue", NULL);
        _pending = [[NSMutableSet alloc] init];
        _cache = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (void)dealloc {
    // The logic for cleaning up the dispatch queue isn't written, so just make sure the singleton
    // never gets dealloced.
    assert(false);
    [super dealloc];
}

- (void)getAddressForHost:(NSString *)hostname
               completion:(void (^)(BOOL, NSString *))completion {
    if (![iTermAdvancedSettingsModel performDNSLookups]) {
        completion(YES, hostname);
        return;
    }

    NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
    @synchronized(self) {
        if ([_pending containsObject:hostname]) {
            DLog(@"Already pending %@", hostname);
            return;
        }
        NSNumber *okNumber = _cache[hostname];
        if (okNumber) {
            completion([okNumber boolValue], hostname);
            return;
        }
        [_pending addObject:hostname];
    }
    dispatch_async(_queue, ^() {
        @synchronized(self) {
            if (![_pending containsObject:hostname]) {
                DLog(@"Abort nslookup for %@", hostname);
                return;
            }
        }

        BOOL shouldCache;
        BOOL ok = [self hostnameHasAddress:[hostname UTF8String] shouldCache:&shouldCache];
        if (shouldCache) {
            @synchronized(self) {
                _cache[hostname] = @(ok);
            }
        }
        dispatch_async(dispatch_get_main_queue(), ^() {
            @synchronized(self) {
                if (![_pending containsObject:hostname]) {
                    DLog(@"Finished nslookup but don't call block for %@", hostname);
                    return;
                }
                [_pending removeObject:hostname];
            }
            DLog(@"Host %@: %@", hostname, ok ? @"OK" : @"Unknown");
            completion(ok, hostname);
        });
    });
    DLog(@"Blocked main thread for %f sec", [NSDate timeIntervalSinceReferenceDate] - start);
}

- (void)cancelRequestForHostname:(NSString *)hostname {
    @synchronized(self) {
        [_pending removeObject:hostname];
    }
}

- (BOOL)hostnameHasAddress:(const char *)hostname shouldCache:(BOOL *)shouldCache {
    struct addrinfo *servinfo;
    int status = getaddrinfo(hostname, NULL, NULL, &servinfo);
    if (status == 0) {
        freeaddrinfo(servinfo);
    }
    // Don't cache temporary failures
    *shouldCache = (status != EAI_AGAIN);
    return status == 0;
}

@end
#import "AsyncHostLookupController.h"
// Notifications posted when hostname lookups finish. Notifications are used to
// avoid dangling references.
static NSString *const kHostnameLookupFailed = @"kHostnameLookupFailed";
static NSString *const kHostnameLookupSucceeded = @"kHostnameLookupSucceeded";
// Set the hostname this view is currently waiting for AsyncHostLookupController to finish looking
// up.
@property(nonatomic, copy) NSString *currentUnderlineHostname;
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(hostnameLookupFailed:)
                                                     name:kHostnameLookupFailed
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(hostnameLookupSucceeded:)
                                                     name:kHostnameLookupSucceeded
                                                   object:nil];
    if (self.currentUnderlineHostname) {
        [[AsyncHostLookupController sharedInstance] cancelRequestForHostname:self.currentUnderlineHostname];
    }
    [_currentUnderlineHostname release];
    _drawingHelper.haveUnderlinedHostname = (self.currentUnderlineHostname != nil);
    if (self.currentUnderlineHostname) {
        [[AsyncHostLookupController sharedInstance] cancelRequestForHostname:self.currentUnderlineHostname];
    }
    self.currentUnderlineHostname = nil;
                    if (![url.host isEqualToString:self.currentUnderlineHostname]) {
                        if (self.currentUnderlineHostname) {
                            [[AsyncHostLookupController sharedInstance] cancelRequestForHostname:self.currentUnderlineHostname];
                        }
                        if (url && url.host) {
                            self.currentUnderlineHostname = url.host;
                            [[AsyncHostLookupController sharedInstance] getAddressForHost:url.host
                                                                               completion:^(BOOL ok, NSString *hostname) {
                                                                                   if (!ok) {
                                                                                       [[NSNotificationCenter defaultCenter] postNotificationName:kHostnameLookupFailed
                                                                                                                                           object:hostname];
                                                                                   } else {
                                                                                       [[NSNotificationCenter defaultCenter] postNotificationName:kHostnameLookupSucceeded
                                                                                                                                           object:hostname];
                                                                                   }
                                                                               }];
                        }
                } else {
                    if (self.currentUnderlineHostname) {
                        [[AsyncHostLookupController sharedInstance] cancelRequestForHostname:self.currentUnderlineHostname];
                    }
                    self.currentUnderlineHostname = nil;
- (void)hostnameLookupFailed:(NSNotification *)notification {
    if ([[notification object] isEqualToString:self.currentUnderlineHostname]) {
        self.currentUnderlineHostname = nil;
        [self removeUnderline];
        _drawingHelper.underlinedRange =
            VT100GridAbsWindowedRangeMake(VT100GridAbsCoordRangeMake(-1, -1, -1, -1), 0, 0);
        [self setNeedsDisplay:YES];
    }
}

- (void)hostnameLookupSucceeded:(NSNotification *)notification {
    if ([[notification object] isEqualToString:self.currentUnderlineHostname]) {
        self.currentUnderlineHostname = nil;
        [self setNeedsDisplay:YES];
    }
}

+ (BOOL)performDNSLookups;
DEFINE_BOOL(performDNSLookups, NO, @"Semantic History: Perform DNS lookups to check if URLs are valid?\nWhen enabled, the name under the mouse will be resolved with DNS to determine if it is a clickable link.");
// Is there an underlined hostname?
@property(nonatomic, assign) BOOL haveUnderlinedHostname;

    BOOL haveUnderlinedHostname;
    } else if (inUnderlinedRange && !context->haveUnderlinedHostname) {
        .haveUnderlinedHostname = _haveUnderlinedHostname,

Fixed code:
                    if (url && url.host) {
                        [self setNeedsDisplay:YES];
    } else if (inUnderlinedRange) {