You need create a copy constructor. This has to do the rule of 3/5. You are creating obj2, which means a copy constructor is invoked, not the copy assignment operator. 
Because you don't have a copy constructor, a "shallow" copy is made. This means that line is copied by value. Since it's a pointer, both obj and obj2 are pointing to the same memory. The first destructor gets called and erases that memory just fine. The second constructor gets called and a double delete occurs, causing your segmentation fault.
class MyClass {
public:
  char *line = nullptr;
  std::size_t size_ = 0;  // Need to know the size at all times, can't 
                          // rely on null character existing
  const std::size_t MAX_SIZE = 256;  // Arbitrarily chosen value
  MyClass() { }
  MyClass(const char *s) : size_(strlen(s)) {
    if (size_ > MAX_SIZE) size_ = MAX_SIZE;
    line = new char[size_];
    strncpy(line, s, size_ - 1);  // 'n' versions are better
    line[size_ - 1] = '\0';
  }
  MyClass(const MyClass& other) : size_(other.size_) {  // Copy constructor
    line = new char[size_ + 1];
    strncpy(line, other.line, size_);
    line[size_] = '\0';
  }
  ~MyClass() {
    delete[] line;
    line = nullptr;
  }
  MyClass& operator=(const MyClass &other) {
    if (line == other.line) return *this;  // Self-assignment guard
    size_ = other.size_;
    delete[] line;
    line = new char[other.size_ + 1];
    strncpy(line, other.line, size_);
    line[size_] = '\0';
    return *this;
  }
  int len(void) const { return size_; }
};
When dealing with C-Strings, you absolutely cannot lose the null character. The issue is that it's extremely easy to lose. You were also lacking a self-assignment guard in your copy assignment operator. That could have led to you accidentally nuking an object. I added a size_ member and used strncpy() instead of strcpy() because being able to specify a maximum number of characters is incredibly important in the case of losing a null character. It won't prevent damage, but it will mitigate it. 
There's some other stuff that I did like using Default Member Initialization(as of C++11) and using a constructor member initialization list. A lot of this becomes unnecessary if you are able to use std::string. C++ can be "C with classes" but it's worth taking the time to really explore what the language has to offer. 
Something that a working copy constructor and destructor allows us to do is simplify our copy assignment operator using the "copy and swap idiom." 
#include <utility>
MyClass& operator=(MyClass tmp) { // Copy by value now
  std::swap(*this, tmp);
  return *this;
}
Link to explanation.